diff --git a/examples/src/docs/main.ts b/examples/src/docs/main.ts index ccc48773158d..a5f8071a8967 100644 --- a/examples/src/docs/main.ts +++ b/examples/src/docs/main.ts @@ -25,7 +25,7 @@ import { UniverDocsThreadCommentUIPlugin } from '@univerjs/docs-thread-comment-u import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; -import { DEFAULT_DOCUMENT_DATA_SIMPLE } from '@univerjs/mockdata'; +import { DEFAULT_DOCUMENT_DATA_CN } from '@univerjs/mockdata'; import { UniverUIPlugin } from '@univerjs/ui'; import { enUS, faIR, ruRU, zhCN } from '../locales'; @@ -71,7 +71,7 @@ univer.registerPlugin(UniverDocsHyperLinkUIPlugin); univer.registerPlugin(UniverDocsMentionUIPlugin); if (!IS_E2E) { - univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_SIMPLE); + univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_CN); } // use for console test diff --git a/mockdata/src/docs/default-document-data-cn.ts b/mockdata/src/docs/default-document-data-cn.ts index 5e241f65c3ac..cecfa25c9627 100644 --- a/mockdata/src/docs/default-document-data-cn.ts +++ b/mockdata/src/docs/default-document-data-cn.ts @@ -15,7 +15,7 @@ */ import type { IDocumentData } from '@univerjs/core'; -import { BooleanNumber, DocumentFlavor } from '@univerjs/core'; +import { BooleanNumber, ColumnSeparatorType, DocumentFlavor, PageOrientType, SectionType } from '@univerjs/core'; import { ptToPixel } from '@univerjs/engine-render'; export const DEFAULT_DOCUMENT_DATA_CN: IDocumentData = { @@ -27,7 +27,7 @@ export const DEFAULT_DOCUMENT_DATA_CN: IDocumentData = { footers: {}, body: { dataStream: - '荷塘月色\r\r作者:朱自清\r\r这几天心里颇不宁静。今晚在院子里坐着乘凉,忽然想起日日走过的荷塘,在这满月的光里,总该另有一番样子吧。月亮渐渐地升高了,墙外马路上孩子们的欢笑,已经听不见了;妻在屋里拍着闰儿,迷迷糊糊地哼着眠歌。我悄悄地披了大衫,带上门出去。\r\r沿着荷塘,是一条曲折的小煤屑路。这是一条幽僻的路;白天也少人走,夜晚更加寂寞。荷塘四面,长着许多树,蓊蓊郁郁的。路图片一片是些杨柳,和一些不知道名字的树。没有月光的晚上,这路上阴森森的,有些怕人。今晚却很好,虽然月光也还是淡淡的。\r\r路上只我一个人,背着手踱着。这一片天地好像是我的;我也像超出了平常的自己,到了另一个世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫的月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话是现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。\r\r曲曲折折的荷塘上面,弥望的是田田的叶子。叶子出水很高,像亭亭的舞女的裙。层层的叶子中间,零星地点缀着些白花,有袅娜地开着的,有羞涩地打着朵儿的;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉的流水,遮住了,不能见一些颜色;而叶子却更见风致了。\r\r月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样,又像笼着轻纱的梦。虽然是满月,天上却有一层淡淡的云,所以不能朗照;但我以为这恰是到了好处——酣眠固不可少,小睡也别有风味的。月光是隔了树照过来的,高处丛生的灌木,落下参差的斑驳的黑影,峭楞楞如鬼一般;弯弯的杨柳的稀疏的倩影,却又像是画在荷叶上。塘中的月色并不均匀;但光与影有着和谐的旋律,如梵婀玲上奏着的名曲。\r\r荷塘的四面,远远近近,高高低低都是树,而杨柳最多。这些树将一片荷塘重重围住;只在小路一旁,漏着几段空隙,像是特为月光留下的。树色一例是阴阴的,乍看像一团烟雾;但杨柳的丰姿,便在烟雾里也辨得出。树梢上隐隐约约的是一带远山,只有些大意罢了。树缝里也漏着一两点路灯光,没精打采的,是渴睡人的眼。这时候最热闹的,要数树上的蝉声与水里的蛙声;但热闹是它们的,我什么也没有。\r\r忽然想起采莲的事情来了。采莲是江南的旧俗,似乎很早就有,而六朝时为盛;从诗歌里可以约略知道。采莲的是少年的女子,她们是荡着小船,唱着艳歌去的。采莲人不用说很多,还有看采莲的人。那是一个热闹的季节,也是一个风流的季节。梁元帝《采莲赋》里说得好:\r\r于是妖童女,荡舟心许;鷁首徐回,兼传羽杯;櫂将移而藻挂,船欲动而萍开。尔其纤腰束素,迁延顾步;夏始春余,叶嫩花初,恐沾裳而浅笑,畏倾船而敛裾。\r\r可见当时嬉游的光景了。这真是有趣的事,可惜我们现在早已无福消受了。\r\r于是又记起,《西洲曲》里的句子:\r\r采莲南塘秋,莲花过人头;低头弄莲子,莲子清如水。\r\r今晚若有采莲人,这儿的莲花也算得“过人头”了;只不见一些流水的影子,是不行的。这令我到底惦着江南了。——这样想着,猛一抬头,不觉已是自己的门前;轻轻地推门进去,什么声息也没有,妻已睡熟好久了。\r\r一九二七年七月,北京清华园。\r\r\r\r《荷塘月色》语言朴素典雅,准确生动,贮满诗意,满溢着朱自清的散文语言一贯有朴素的美,不用浓墨重彩,画的是淡墨水彩。\r\r朱自清先生一笔写景一笔说情,看起来松散不知所云,可仔细体会下,就能感受到先生在字里行间表述出的苦闷,而随之读者也被先生的文字所感染,被带进了他当时那苦闷而无法明喻的心情。这就是优异散文的必须品质之一。\r\r扩展资料:\r一首长诗《毁灭》奠定了朱自清在文坛新诗人的地位,而《桨声灯影里的秦淮河》则被公认为白话美文的典范。朱自清用白话美文向复古派宣战,有力地回击了复古派“白话不能作美文”之说,他是“五四”新文学运动的开拓者之一。\r\r朱自清的美文影响了一代又一代人。作家贾平凹说:来到扬州,第一个想到的人是朱自清,他是知识分子中最最了不起的人物。\r\r实际上,朱自清的写作路程是非常曲折的,他早期的时候大多数作品都是诗歌,但是他的诗歌和我国古代诗人的诗有很大区别,他的诗是用白话文写的,这其实也是他写作的惯用风格。\r\r后来,朱自清开始写一些关于社会的文章,因为那个时候社会比较混乱,这时候的作品大多抨击社会的黑暗面,文体风格大多硬朗,基调伉俪。到了后期,大多是写关于山水的文章,这类文章的写作格调大多以清丽雅致为主。\r\r朱自清的写作风格虽然在不同的时期随着他的人生阅历和社会形态的不同而发生着变化,但是他文章的主基调是没有变的,他这一生,所写的所有文章风格上都有一个非常显著的特点,那就是简约平淡,他不是类似古代花间词派的诗人们,不管是他的诗词还是他的文章从来都不用过于华丽的辞藻,他崇尚的是平淡。\r\r英国友人戴立克试过英译朱自清几篇散文,译完一读显得单薄,远远不如原文流利。他不服气,改用稍微古奥的英文重译,好多了:“那是说,朱先生外圆内方,文字尽管浅白,心思却很深沉,译笔只好朝深处经营。”朱自清的很多文章,譬如《背影》《祭亡妇》,读来自有一番只可意会不可言传的东西。\r\r平淡就是朱自清的写作风格。他不是豪放派的作家,他在创作的时候钟情于清新的风格,给人耳目一新的感觉。在他的文章中包含了他对生活的向往,由此可见他的写作风格和他待人处事的态度也是有几分相似的。他的文章非常优美,但又不会让人觉得狭隘,给人一种豁达渊博的感觉,这就是朱自清的写作风格,更是朱自清的为人品质。\r\r写有《荷塘月色》《背影》等名篇的著名散文家朱自清先生,不仅自己一生风骨正气,还用无形的家风涵养子孙。良好的家风家规意蕴深远,催人向善,是凝聚情感、涵养德行、砥砺成才的人生信条。“北有朱自清,南有朱物华,一文一武,一南一北,双星闪耀”,这是中国知识界、教育界对朱家两兄弟的赞誉。\r\r朱自清性格温和,为人和善,对待年轻人平易近人,是个平和的人。他取字“佩弦”,意思要像弓弦那样将自己绷紧,给人的感觉是自我要求高,偶尔有呆气。朱自清教学负责,对学生要求严格,修他的课的学生都受益不少。\r\r1948 年 6 月,患胃病多年的朱自清,在《抗议美国扶日政策并拒绝领取美援面粉宣言》上,一丝不苟地签下了自己的名字。随后,朱自清还将面粉配购证以及面粉票退了回去。1948 年 8 月 12 日,朱自清因不堪胃病折磨,离开人世。在新的时代即将到来时,朱自清却匆匆地离人们远去。他为人们留下了无数经典的诗歌和文字,还有永不屈服的精神。\r\r朱自清没有豪言壮语,他只是用坚定的行动、朴实的语言,向世人展示了中国知识分子在祖国危难之际坚定的革命性,体现了中国人的骨气,表现了无比高贵的民族气节,呈现了人生最有价值的一面,谱就了生命中最华丽的乐章。\r\r他以“自清”为名,自勉在困境中不丧志;他身患重病,至死拒领美援面粉,其气节令世人感佩;他的《背影》《荷塘月色》《匆匆》脍炙人口;他的文字追求“真”,没有半点矫饰,却蕴藏着动人心弦的力量。\r\r朱自清不但在文学创作方面有很高的造诣,也是一名革命民主主义战士,在反饥饿、反内战的斗争中,他始终保持着一个正直的爱国知识分子的气节和情操。毛泽东对朱自清宁肯饿死不领美国“救济粉”的精神给予称赞,赞扬他“表现了我们民族的英雄气概”。\r\n', + '荷塘月色\r\r作者:朱自清\r\r这几天心里颇不宁静。今晚在院子里坐着乘凉,忽然想起日日走过的荷塘,在这满月的光里,总该另有一番样子吧。月亮渐渐地升高了,墙外马路上孩子们的欢笑,已经听不见了;妻在屋里拍着闰儿,迷迷糊糊地哼着眠歌。我悄悄地披了大衫,带上门出去。\r\r沿着荷塘,是一条曲折的小煤屑路。这是一条幽僻的路;白天也少人走,夜晚更加寂寞。荷塘四面,长着许多树,蓊蓊郁郁的。路图片一片是些杨柳,和一些不知道名字的树。没有月光的晚上,这路上阴森森的,有些怕人。今晚却很好,虽然月光也还是淡淡的。\r\r路上只我一个人,背着手踱着。这一片天地好像是我的;我也像超出了平常的自己,到了另一个世界里。我爱热闹,也爱冷静;爱群居,也爱独处。像今晚上,一个人在这苍茫的月下,什么都可以想,什么都可以不想,便觉是个自由的人。白天里一定要做的事,一定要说的话是现在都可不理。这是独处的妙处,我且受用这无边的荷香月色好了。\r\n曲曲折折的荷塘上面,弥望的是田田的叶子。叶子出水很高,像亭亭的舞女的裙。层层的叶子中间,零星地点缀着些白花,有袅娜地开着的,有羞涩地打着朵儿的;正如一粒粒的明珠,又如碧天里的星星,又如刚出浴的美人。微风过处,送来缕缕清香,仿佛远处高楼上渺茫的歌声似的。这时候叶子与花也有一丝的颤动,像闪电般,霎时传过荷塘的那边去了。叶子本是肩并肩密密地挨着,这便宛然有了一道凝碧的波痕。叶子底下是脉脉的流水,遮住了,不能见一些颜色;而叶子却更见风致了。\r\r月光如流水一般,静静地泻在这一片叶子和花上。薄薄的青雾浮起在荷塘里。叶子和花仿佛在牛乳中洗过一样,又像笼着轻纱的梦。虽然是满月,天上却有一层淡淡的云,所以不能朗照;但我以为这恰是到了好处——酣眠固不可少,小睡也别有风味的。月光是隔了树照过来的,高处丛生的灌木,落下参差的斑驳的黑影,峭楞楞如鬼一般;弯弯的杨柳的稀疏的倩影,却又像是画在荷叶上。塘中的月色并不均匀;但光与影有着和谐的旋律,如梵婀玲上奏着的名曲。\r\r荷塘的四面,远远近近,高高低低都是树,而杨柳最多。这些树将一片荷塘重重围住;只在小路一旁,漏着几段空隙,像是特为月光留下的。树色一例是阴阴的,乍看像一团烟雾;但杨柳的丰姿,便在烟雾里也辨得出。树梢上隐隐约约的是一带远山,只有些大意罢了。树缝里也漏着一两点路灯光,没精打采的,是渴睡人的眼。这时候最热闹的,要数树上的蝉声与水里的蛙声;但热闹是它们的,我什么也没有。\r\r忽然想起采莲的事情来了。采莲是江南的旧俗,似乎很早就有,而六朝时为盛;从诗歌里可以约略知道。采莲的是少年的女子,她们是荡着小船,唱着艳歌去的。采莲人不用说很多,还有看采莲的人。那是一个热闹的季节,也是一个风流的季节。梁元帝《采莲赋》里说得好:\r\r于是妖童女,荡舟心许;鷁首徐回,兼传羽杯;櫂将移而藻挂,船欲动而萍开。尔其纤腰束素,迁延顾步;夏始春余,叶嫩花初,恐沾裳而浅笑,畏倾船而敛裾。\r\r可见当时嬉游的光景了。这真是有趣的事,可惜我们现在早已无福消受了。\r\r于是又记起,《西洲曲》里的句子:\r\r采莲南塘秋,莲花过人头;低头弄莲子,莲子清如水。\r\r今晚若有采莲人,这儿的莲花也算得“过人头”了;只不见一些流水的影子,是不行的。这令我到底惦着江南了。——这样想着,猛一抬头,不觉已是自己的门前;轻轻地推门进去,什么声息也没有,妻已睡熟好久了。\r\r一九二七年七月,北京清华园。\r\r\r\r《荷塘月色》语言朴素典雅,准确生动,贮满诗意,满溢着朱自清的散文语言一贯有朴素的美,不用浓墨重彩,画的是淡墨水彩。\r\r朱自清先生一笔写景一笔说情,看起来松散不知所云,可仔细体会下,就能感受到先生在字里行间表述出的苦闷,而随之读者也被先生的文字所感染,被带进了他当时那苦闷而无法明喻的心情。这就是优异散文的必须品质之一。\r\r扩展资料:\r一首长诗《毁灭》奠定了朱自清在文坛新诗人的地位,而《桨声灯影里的秦淮河》则被公认为白话美文的典范。朱自清用白话美文向复古派宣战,有力地回击了复古派“白话不能作美文”之说,他是“五四”新文学运动的开拓者之一。\r\r朱自清的美文影响了一代又一代人。作家贾平凹说:来到扬州,第一个想到的人是朱自清,他是知识分子中最最了不起的人物。\r\r实际上,朱自清的写作路程是非常曲折的,他早期的时候大多数作品都是诗歌,但是他的诗歌和我国古代诗人的诗有很大区别,他的诗是用白话文写的,这其实也是他写作的惯用风格。\r\r后来,朱自清开始写一些关于社会的文章,因为那个时候社会比较混乱,这时候的作品大多抨击社会的黑暗面,文体风格大多硬朗,基调伉俪。到了后期,大多是写关于山水的文章,这类文章的写作格调大多以清丽雅致为主。\r\r朱自清的写作风格虽然在不同的时期随着他的人生阅历和社会形态的不同而发生着变化,但是他文章的主基调是没有变的,他这一生,所写的所有文章风格上都有一个非常显著的特点,那就是简约平淡,他不是类似古代花间词派的诗人们,不管是他的诗词还是他的文章从来都不用过于华丽的辞藻,他崇尚的是平淡。\r\r英国友人戴立克试过英译朱自清几篇散文,译完一读显得单薄,远远不如原文流利。他不服气,改用稍微古奥的英文重译,好多了:“那是说,朱先生外圆内方,文字尽管浅白,心思却很深沉,译笔只好朝深处经营。”朱自清的很多文章,譬如《背影》《祭亡妇》,读来自有一番只可意会不可言传的东西。\r\r平淡就是朱自清的写作风格。他不是豪放派的作家,他在创作的时候钟情于清新的风格,给人耳目一新的感觉。在他的文章中包含了他对生活的向往,由此可见他的写作风格和他待人处事的态度也是有几分相似的。他的文章非常优美,但又不会让人觉得狭隘,给人一种豁达渊博的感觉,这就是朱自清的写作风格,更是朱自清的为人品质。\r\r写有《荷塘月色》《背影》等名篇的著名散文家朱自清先生,不仅自己一生风骨正气,还用无形的家风涵养子孙。良好的家风家规意蕴深远,催人向善,是凝聚情感、涵养德行、砥砺成才的人生信条。“北有朱自清,南有朱物华,一文一武,一南一北,双星闪耀”,这是中国知识界、教育界对朱家两兄弟的赞誉。\r\r朱自清性格温和,为人和善,对待年轻人平易近人,是个平和的人。他取字“佩弦”,意思要像弓弦那样将自己绷紧,给人的感觉是自我要求高,偶尔有呆气。朱自清教学负责,对学生要求严格,修他的课的学生都受益不少。\r\r1948 年 6 月,患胃病多年的朱自清,在《抗议美国扶日政策并拒绝领取美援面粉宣言》上,一丝不苟地签下了自己的名字。随后,朱自清还将面粉配购证以及面粉票退了回去。1948 年 8 月 12 日,朱自清因不堪胃病折磨,离开人世。在新的时代即将到来时,朱自清却匆匆地离人们远去。他为人们留下了无数经典的诗歌和文字,还有永不屈服的精神。\r\r朱自清没有豪言壮语,他只是用坚定的行动、朴实的语言,向世人展示了中国知识分子在祖国危难之际坚定的革命性,体现了中国人的骨气,表现了无比高贵的民族气节,呈现了人生最有价值的一面,谱就了生命中最华丽的乐章。\r\r他以“自清”为名,自勉在困境中不丧志;他身患重病,至死拒领美援面粉,其气节令世人感佩;他的《背影》《荷塘月色》《匆匆》脍炙人口;他的文字追求“真”,没有半点矫饰,却蕴藏着动人心弦的力量。\r\r朱自清不但在文学创作方面有很高的造诣,也是一名革命民主主义战士,在反饥饿、反内战的斗争中,他始终保持着一个正直的爱国知识分子的气节和情操。毛泽东对朱自清宁肯饿死不领美国“救济粉”的精神给予称赞,赞扬他“表现了我们民族的英雄气概”。\r\n', textRuns: [ { st: 0, @@ -159,14 +159,14 @@ export const DEFAULT_DOCUMENT_DATA_CN: IDocumentData = { spaceBelow: { v: 0 }, }, }, - { - startIndex: 399, - paragraphStyle: { - spaceAbove: { v: 10 }, - lineSpacing: 2, - spaceBelow: { v: 0 }, - }, - }, + // { + // startIndex: 399, + // paragraphStyle: { + // spaceAbove: { v: 10 }, + // lineSpacing: 2, + // spaceBelow: { v: 0 }, + // }, + // }, { startIndex: 618, paragraphStyle: { @@ -585,16 +585,41 @@ export const DEFAULT_DOCUMENT_DATA_CN: IDocumentData = { }, ], sectionBreaks: [ + { + startIndex: 399, + pageSize: { + width: ptToPixel(495), + height: ptToPixel(642), + }, + sectionType: SectionType.SECTION_TYPE_UNSPECIFIED, + pageOrient: PageOrientType.PORTRAIT, + marginTop: ptToPixel(20), + marginBottom: ptToPixel(20), + marginRight: ptToPixel(20), + marginLeft: ptToPixel(20), + equalWidth: BooleanNumber.TRUE, + numOfEqualWidthColumns: 2, + spaceBetweenEqualWidthColumns: { v: 10 }, + }, { startIndex: 3066, - // columnProperties: [ - // { - // width: ptToPixel(240), - // paddingEnd: ptToPixel(15), - // }, - // ], - // columnSeparatorType: ColumnSeparatorType.NONE, - // sectionType: SectionType.SECTION_TYPE_UNSPECIFIED, + columnProperties: [ + { + width: ptToPixel(240), + paddingEnd: ptToPixel(11), + }, + { + width: ptToPixel(240), + paddingEnd: ptToPixel(11), + }, + { + width: ptToPixel(240), + paddingEnd: ptToPixel(0), + }, + ], + equalWidth: BooleanNumber.FALSE, + columnSeparatorType: ColumnSeparatorType.NONE, + sectionType: SectionType.NEXT_PAGE, // textDirection: textDirectionDocument, // contentDirection: textDirection!, }, @@ -607,6 +632,7 @@ export const DEFAULT_DOCUMENT_DATA_CN: IDocumentData = { width: ptToPixel(595), height: ptToPixel(842), }, + pageOrient: PageOrientType.LANDSCAPE, documentFlavor: DocumentFlavor.TRADITIONAL, marginTop: ptToPixel(50), marginBottom: ptToPixel(50), @@ -633,5 +659,7 @@ export const DEFAULT_DOCUMENT_DATA_CN: IDocumentData = { useFirstPageHeaderFooter: BooleanNumber.FALSE, marginHeader: 30, marginFooter: 30, + autoSpaceDE: BooleanNumber.TRUE, + autoSpaceDN: BooleanNumber.TRUE, }, }; diff --git a/packages/core/src/docs/data-model/types.ts b/packages/core/src/docs/data-model/types.ts index 8ba7b6a04a75..207bc766f01b 100644 --- a/packages/core/src/docs/data-model/types.ts +++ b/packages/core/src/docs/data-model/types.ts @@ -15,7 +15,7 @@ */ export enum DataStreamTreeNodeType { - // COLUMN_BREAK, // \v 换列 + // COLUMN_BREAK, // \v 换栏 // PAGE_BREAK, // \f 换页 // DOCS_END, // \0 文档结尾 // TAB, // \t 制表符 @@ -47,7 +47,7 @@ export enum DataStreamTreeTokenType { */ CUSTOM_RANGE_END = '\x1E', // 自定义范围结束 - COLUMN_BREAK = '\v', // 换列 + COLUMN_BREAK = '\v', // 换栏 PAGE_BREAK = '\f', // 换页 DOCS_END = '\0', // 文档结尾 TAB = '\t', // 制表符 diff --git a/packages/core/src/types/interfaces/i-document-data.ts b/packages/core/src/types/interfaces/i-document-data.ts index 82924e08c15c..3ba6ba630b4a 100644 --- a/packages/core/src/types/interfaces/i-document-data.ts +++ b/packages/core/src/types/interfaces/i-document-data.ts @@ -429,7 +429,7 @@ export enum DocumentFlavor { export interface IDocStyleBase extends IMargin { pageNumberStart?: number; // pageNumberStart pageSize?: ISize; // pageSize - + // PORTRAIT and LANDSCAPE pageOrient?: PageOrientType; documentFlavor?: DocumentFlavor; // DocumentFlavor: TRADITIONAL, MODERN @@ -445,8 +445,10 @@ export interface IDocumentLayout { defaultTabStop?: number; // 17.15.1.25 defaultTabStop (Distance Between Automatic Tab Stops) 0.5 in = 36pt,this value should be converted to the default font size when exporting characterSpacingControl?: characterSpacingControlType; // characterSpacingControl 17.18.7 ST_CharacterSpacing (Character-Level Whitespace Compression Settings),default compressPunctuation paragraphLineGapDefault?: number; // paragraphLineGapDefault default line spacing - spaceWidthEastAsian?: BooleanNumber; // add space between east asian and English + autoSpaceDE?: BooleanNumber; // 17.3.1.2 autoSpaceDE (Automatically Adjust Spacing of Latin and East Asian Text) + autoSpaceDN?: BooleanNumber; // 17.3.1.3 autoSpaceDN (Automatically Adjust Spacing of East Asian Text and Numbers) + // Hyphenation. autoHyphenation?: BooleanNumber; // 17.15.1.10 autoHyphenation (Automatically Hyphenate Document Contents When Displayed) consecutiveHyphenLimit?: number; // 17.15.1.22 consecutiveHyphenLimit (Maximum Number of Consecutively Hyphenated Lines) doNotHyphenateCaps?: BooleanNumber; // 17.15.1.37 doNotHyphenateCaps (Do Not Hyphenate Words in ALL CAPITAL LETTERS) @@ -500,6 +502,27 @@ export interface ISectionBreakBase { columnProperties?: ISectionColumnProperties[]; // columnProperties 17.6.4 cols (Column Definitions) columnSeparatorType?: ColumnSeparatorType; // ColumnSeparatorType + // If this attribute is present and its value is set to true or 1, then all columns for this text + // section are of an equal width and are calculated as follows: + // - Take width of page (from margin to margin) + // - Divide by number of columns specified in num attribute + // - For each column, leave space after as defined in the space attribute + // - Remaining width of each column is the text column width. + // If this attribute is present and its value is set to false or 0, then all columns for this text + // section are of different widths and are defined by each col element as follows: + // - Each col element defines a single column + // - Each w attribute defines the text column width + // - Each space attribute defines the space after the text column + equalWidth?: BooleanNumber; // Specifies whether all text columns in the current section are of equal width. 17.6.4 cols (Column Definitions) + // If all columns are not of equal width (the equalWidth attribute is not set), then this + // element is ignored, and the number of columns is defined by the number of col elements + // defined under the cols element. + numOfEqualWidthColumns?: number; // Specifies the number of text columns in the current section. + // Specifies the spacing between text columns in the current section. + // If all columns are not of equal width (the equalWidth attribute is not set), then this + // element is ignored, and the spacing after columns is defined by the space attribute on + // each of the col elements defined under the cols element. + spaceBetweenEqualWidthColumns?: INumberUnit; contentDirection?: TextDirection; // contentDirection sectionType?: SectionType; // sectionType 17.6.22 type (Section Type) // deprecated: The attribute does not exist in Word and should be deprecated. @@ -745,6 +768,8 @@ export interface IParagraphProperties extends IIndentStart { widowControl?: BooleanNumber; // 17.3.1.44 widowControl (Allow First/Last Line to Display on a Separate Page) shading?: IShading; // shading suppressHyphenation?: BooleanNumber; // 17.3.1.34 suppressAutoHyphens (Suppress Hyphenation for Paragraph) + autoSpaceDE?: BooleanNumber; // 17.3.1.2 autoSpaceDE (Automatically Adjust Spacing of Latin and East Asian Text) + autoSpaceDN?: BooleanNumber; // 17.3.1.3 autoSpaceDN (Automatically Adjust Spacing of East Asian Text and Numbers) } /** @@ -1019,11 +1044,25 @@ export interface IObjectPositionV { // 20.4.3.4 ST_RelFromH (Horizontal Relative Positioning) export enum ObjectRelativeFromH { + // Specifies that the horizontal positioning shall be + // relative to the edge of the page. PAGE, + // Specifies that the horizontal positioning shall be + // relative to the extents of the column which contains its anchor. COLUMN, + // Specifies that the horizontal positioning shall be + // relative to the position of the anchor within its run content. CHARACTER, - MARGIN, + // Specifies that the horizontal positioning shall be + // relative to the inside margin of the current page (the + // left margin on odd pages, right on even pages) INSIDE_MARGIN, + // Specifies that the horizontal positioning shall be + // relative to the page margins. + MARGIN, + // Specifies that the horizontal positioning shall be + // relative to the outside margin of the current page (the + // right margin on odd pages, left on even pages). OUTSIDE_MARGIN, LEFT_MARGIN, RIGHT_MARGIN, @@ -1031,11 +1070,23 @@ export enum ObjectRelativeFromH { // 20.4.3.4 ST_RelFromH (Horizontal Relative Positioning) export enum ObjectRelativeFromV { + // Specifies that the vertical positioning shall be relative + // to the edge of the page. PAGE, + // Specifies that the vertical positioning shall be relative + // to the paragraph which contains the drawing anchor. PARAGRAPH, + // Specifies that the vertical positioning shall be relative + // to the page margins. LINE, + // Specifies that the vertical positioning shall be relative + // to the page margins. MARGIN, + // Specifies that the vertical positioning shall be relative + // to the top margin of the current page. TOP_MARGIN, + // Specifies that the vertical positioning shall be relative + // to the bottom margin of the current page. BOTTOM_MARGIN, INSIDE_MARGIN, OUTSIDE_MARGIN, diff --git a/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts b/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts index 74b45605ced6..9c3f9c21068c 100644 --- a/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts +++ b/packages/docs-drawing-ui/src/commands/commands/update-doc-drawing.command.ts @@ -332,7 +332,7 @@ export const UpdateDocDrawingWrappingStyleCommand: ICommand = { if (oldPositionH.relativeFrom === ObjectRelativeFromH.MARGIN) { posOffsetH -= pageMarginLeft; } else if (oldPositionH.relativeFrom === ObjectRelativeFromH.COLUMN) { - posOffsetH -= skeDrawing.columnLeft; + posOffsetH = posOffsetH - skeDrawing.columnLeft - pageMarginLeft; } const newPositionH = { @@ -349,12 +349,12 @@ export const UpdateDocDrawingWrappingStyleCommand: ICommand = { const oldPositionV = oldDrawings[drawingId].docTransform.positionV; let posOffsetV = aTop; - if (oldPositionV.relativeFrom === ObjectRelativeFromV.PAGE) { - posOffsetV += pageMarginTop; + if (oldPositionV.relativeFrom === ObjectRelativeFromV.MARGIN) { + posOffsetV = posOffsetV - pageMarginTop; } else if (oldPositionV.relativeFrom === ObjectRelativeFromV.LINE) { - posOffsetV -= skeDrawing.lineTop; + posOffsetV = posOffsetV - pageMarginTop - skeDrawing.lineTop; } else if (oldPositionV.relativeFrom === ObjectRelativeFromV.PARAGRAPH) { - posOffsetV -= skeDrawing.blockAnchorTop; + posOffsetV = posOffsetV - pageMarginTop - skeDrawing.blockAnchorTop; } const newPositionV = { diff --git a/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts b/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts index 9d1809fd4268..8687b902bc44 100644 --- a/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts +++ b/packages/docs-drawing-ui/src/controllers/doc-drawing-transformer-update.controller.ts @@ -501,6 +501,7 @@ export class DocDrawingTransformerController extends Disposable { segmentId: docSelectionRenderService.getSegment(), segmentPage: docSelectionRenderService.getSegmentPage(), }); + if (nodeInfo) { const { node, segmentPage: segmentPageIndex, segmentId: nodeSegmentId } = nodeInfo; glyphAnchor = node; @@ -526,9 +527,12 @@ export class DocDrawingTransformerController extends Disposable { const pageType = page.type; for (const p of pages) { - const { headerId, footerId, pageHeight, pageWidth, marginLeft, marginBottom } = p; + const { headerId, footerId, pageHeight, pageWidth, marginLeft, marginBottom, left: pageLeft } = p; const pIndex = pages.indexOf(p); + this._liquid.translateSave(); + this._liquid.translate(pageLeft, 0); + if (segmentPage > -1 && pIndex === segmentPage) { switch (pageType) { case DocumentSkeletonPageType.HEADER: { @@ -571,6 +575,7 @@ export class DocDrawingTransformerController extends Disposable { } this._liquid.restorePagePadding(p); + this._liquid.translateRestore(); this._liquid.translatePage(p, pageLayoutType, pageMarginLeft, pageMarginTop); } @@ -580,14 +585,15 @@ export class DocDrawingTransformerController extends Disposable { glyphAnchor = paragraphStartLine.divides?.[0]?.glyphGroup?.[0] ?? glyphAnchor; } + // docTransform.positionH = { relativeFrom: positionH.relativeFrom, posOffset: left - this._liquid.x - docsLeft, }; switch (positionH.relativeFrom) { - case ObjectRelativeFromH.MARGIN: { - docTransform.positionH.posOffset = left - this._liquid.x - docsLeft - page.marginLeft; + case ObjectRelativeFromH.PAGE: { + docTransform.positionH.posOffset = left - this._liquid.x - docsLeft + page.marginLeft; break; } case ObjectRelativeFromH.COLUMN: { @@ -603,7 +609,7 @@ export class DocDrawingTransformerController extends Disposable { switch (positionV.relativeFrom) { case ObjectRelativeFromV.PAGE: { - docTransform.positionV.posOffset = top - this._liquid.y - docsTop - page.marginTop; + docTransform.positionV.posOffset = top - this._liquid.y - docsTop + page.marginTop; break; } case ObjectRelativeFromV.LINE: { diff --git a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-transform-update.controller.ts b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-transform-update.controller.ts index 5103866207e9..dc75134cae3e 100644 --- a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-transform-update.controller.ts +++ b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-transform-update.controller.ts @@ -140,6 +140,7 @@ export class DocDrawingTransformUpdateController extends Disposable implements I ); } + // eslint-disable-next-line max-lines-per-function private _refreshDrawing(skeleton: DocumentSkeleton) { const skeletonData = skeleton?.getSkeletonData(); const { mainComponent, unitId } = this._context; @@ -159,16 +160,23 @@ export class DocDrawingTransformUpdateController extends Disposable implements I */ for (let i = 0, len = pages.length; i < len; i++) { const page = pages[i]; - const { headerId, footerId, pageWidth } = page; + const { headerId, footerId, pageWidth, left: pageLeft } = page; + + this._liquid.translateSave(); + this._liquid.translate(pageLeft, 0); if (headerId) { const headerPage = skeHeaders.get(headerId)?.get(pageWidth); if (headerPage) { this._calculateDrawingPosition( - unitId, headerPage, docsLeft, docsTop, updateDrawingMap, - headerPage.marginTop, - page.marginLeft + unitId, + headerPage, + docsLeft, + docsTop, + updateDrawingMap, + 0, + 0 ); } } @@ -178,14 +186,28 @@ export class DocDrawingTransformUpdateController extends Disposable implements I if (footerPage) { this._calculateDrawingPosition( - unitId, footerPage, docsLeft, docsTop, updateDrawingMap, - page.pageHeight - page.marginBottom + footerPage.marginTop, - page.marginLeft + unitId, + footerPage, + docsLeft, + docsTop, + updateDrawingMap, + 0, + page.pageHeight - page.marginBottom ); } } - this._calculateDrawingPosition(unitId, page, docsLeft, docsTop, updateDrawingMap, page.marginTop, page.marginLeft); + this._calculateDrawingPosition( + unitId, + page, + docsLeft, + docsTop, + updateDrawingMap, + 0, + 0 + ); + + this._liquid.translateRestore(); this._liquid.translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); } @@ -245,14 +267,12 @@ export class DocDrawingTransformUpdateController extends Disposable implements I docsLeft: number, docsTop: number, updateDrawingMap: Record, - marginTop: number, - marginLeft: number + xOffset: number, + yOffset: number ) { const { skeDrawings } = page; - this._liquid.translatePagePadding({ - marginTop, - marginLeft, - } as IDocumentSkeletonPage); + this._liquid.translateSave(); + this._liquid.translate(xOffset, yOffset); skeDrawings.forEach((drawing) => { const { aLeft, aTop, height, width, angle, drawingId, drawingOrigin } = drawing; @@ -280,10 +300,7 @@ export class DocDrawingTransformUpdateController extends Disposable implements I } }); - this._liquid.restorePagePadding({ - marginTop, - marginLeft, - } as IDocumentSkeletonPage); + this._liquid.translateRestore(); } private _drawingInitializeListener() { diff --git a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts index 744a8de5358b..bb72102650dc 100644 --- a/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts +++ b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts @@ -24,7 +24,7 @@ import { MessageType } from '@univerjs/design'; import { DocSelectionManagerService, DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; import { IDocDrawingService } from '@univerjs/docs-drawing'; import { docDrawingPositionToTransform, DocSelectionRenderService } from '@univerjs/docs-ui'; -import { DRAWING_IMAGE_ALLOW_IMAGE_LIST, DRAWING_IMAGE_ALLOW_SIZE, DRAWING_IMAGE_COUNT_LIMIT, DRAWING_IMAGE_HEIGHT_LIMIT, DRAWING_IMAGE_WIDTH_LIMIT, getDrawingShapeKeyByDrawingSearch, getImageSize, IDrawingManagerService, IImageIoService, ImageUploadStatusType } from '@univerjs/drawing'; +import { DRAWING_IMAGE_ALLOW_IMAGE_LIST, DRAWING_IMAGE_ALLOW_SIZE, DRAWING_IMAGE_COUNT_LIMIT, DRAWING_IMAGE_HEIGHT_LIMIT, getDrawingShapeKeyByDrawingSearch, getImageSize, IDrawingManagerService, IImageIoService, ImageUploadStatusType } from '@univerjs/drawing'; import { DocumentEditArea, IRenderManagerService } from '@univerjs/engine-render'; import { ILocalFileService, IMessageService } from '@univerjs/ui'; @@ -78,10 +78,11 @@ export class DocDrawingUpdateRenderController extends Disposable implements IRen } await this._insertFloatImages(files); + return true; } - // eslint-disable-next-line max-lines-per-function + // eslint-disable-next-line max-lines-per-function, complexity private async _insertFloatImages(files: File[]) { let imageParams: Nullable[] = []; @@ -118,6 +119,18 @@ export class DocDrawingUpdateRenderController extends Disposable implements IRen const { unitId } = this._context; const docDrawingParams: IDocDrawing[] = []; + const activeTextRange = this._docSelectionManagerService.getActiveTextRange(); + const skeleton = this._renderManagerSrv.getRenderById(unitId) + ?.with(DocSkeletonManagerService); + + if (activeTextRange == null || skeleton == null) { + return; + } + + const glyph = skeleton.getSkeleton().findNodeByCharIndex(activeTextRange.startOffset); + const section = glyph?.parent?.parent?.parent?.parent; + const minColWidth = Math.min(...section?.columns.map((col) => col.width) ?? []); + for (const imageParam of imageParams) { if (imageParam == null) { continue; @@ -128,8 +141,8 @@ export class DocDrawingUpdateRenderController extends Disposable implements IRen this._imageIoService.addImageSourceCache(imageId, imageSourceType, image); let scale = 1; - if (width > DRAWING_IMAGE_WIDTH_LIMIT || height > DRAWING_IMAGE_HEIGHT_LIMIT) { - const scaleWidth = DRAWING_IMAGE_WIDTH_LIMIT / width; + if (width > minColWidth || height > DRAWING_IMAGE_HEIGHT_LIMIT) { + const scaleWidth = minColWidth / width; const scaleHeight = DRAWING_IMAGE_HEIGHT_LIMIT / height; scale = Math.min(scaleWidth, scaleHeight); } diff --git a/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx b/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx index baadb7d84f15..da0a92cff31e 100644 --- a/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx +++ b/packages/docs-drawing-ui/src/views/doc-image-panel/DocDrawingPosition.tsx @@ -208,17 +208,15 @@ export const DocDrawingPosition = (props: IDocDrawingPositionProps) => { let delta = 0; if (prevRelativeFrom === ObjectRelativeFromH.COLUMN) { - delta -= drawing.columnLeft; + delta = delta - drawing.columnLeft - pageMarginLeft; } else if (prevRelativeFrom === ObjectRelativeFromH.MARGIN) { delta -= pageMarginLeft; } if (relativeFrom === ObjectRelativeFromH.COLUMN) { - delta += drawing.columnLeft; + delta = delta + drawing.columnLeft + pageMarginLeft; } else if (relativeFrom === ObjectRelativeFromH.MARGIN) { delta += pageMarginLeft; - } else if (relativeFrom === ObjectRelativeFromH.PAGE) { - // Do nothing. } const newPositionH = { @@ -275,19 +273,19 @@ export const DocDrawingPosition = (props: IDocDrawingPositionProps) => { let delta = 0; if (prevRelativeFrom === ObjectRelativeFromV.PARAGRAPH) { - delta -= paragraphStartLine.top; + delta = delta - paragraphStartLine.top - page.marginTop; } else if (prevRelativeFrom === ObjectRelativeFromV.LINE) { - delta -= line.top; - } else if (prevRelativeFrom === ObjectRelativeFromV.PAGE) { - delta += page.marginTop; + delta = delta - line.top - page.marginTop; + } else if (prevRelativeFrom === ObjectRelativeFromV.MARGIN) { + delta -= page.marginTop; } if (relativeFrom === ObjectRelativeFromV.PARAGRAPH) { - delta += paragraphStartLine.top; + delta = delta + paragraphStartLine.top + page.marginTop; } else if (relativeFrom === ObjectRelativeFromV.LINE) { - delta += line.top; - } else if (relativeFrom === ObjectRelativeFromV.PAGE) { - delta -= page.marginTop; + delta = delta + line.top + page.marginTop; + } else if (relativeFrom === ObjectRelativeFromV.MARGIN) { + delta += page.marginTop; } const newPositionV = { diff --git a/packages/docs-ui/src/commands/commands/doc-header-footer.command.ts b/packages/docs-ui/src/commands/commands/doc-header-footer.command.ts index 55762f183263..44bc162f4b27 100644 --- a/packages/docs-ui/src/commands/commands/doc-header-footer.command.ts +++ b/packages/docs-ui/src/commands/commands/doc-header-footer.command.ts @@ -29,7 +29,7 @@ function getEmptyHeaderFooterBody(): IDocumentBody { dataStream: '\r\n', textRuns: [{ st: 0, - ed: 0, + ed: 1, ts: { fs: 9, // The default header footer text size. }, diff --git a/packages/docs-ui/src/commands/commands/switch-doc-mode.command.ts b/packages/docs-ui/src/commands/commands/switch-doc-mode.command.ts index 17b86036d6fe..4f7c9721f9a5 100644 --- a/packages/docs-ui/src/commands/commands/switch-doc-mode.command.ts +++ b/packages/docs-ui/src/commands/commands/switch-doc-mode.command.ts @@ -19,6 +19,7 @@ import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import { CommandType, DocumentFlavor, ICommandService, IUniverInstanceService, JSONX, ObjectRelativeFromV } from '@univerjs/core'; import { DocSelectionManagerService, DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; import { IRenderManagerService } from '@univerjs/engine-render'; +import { DocPageLayoutService } from '../../services/doc-page-layout.service'; import { DocSelectionRenderService } from '../../services/selection/doc-selection-render.service'; export interface ISwitchDocModeCommandParams { } @@ -33,7 +34,6 @@ export const SwitchDocModeCommand: ICommand = { const commandService = accessor.get(ICommandService); const renderManagerService = accessor.get(IRenderManagerService); const docSelectionManagerService = accessor.get(DocSelectionManagerService); - const univerInstanceService = accessor.get(IUniverInstanceService); const docDataModel = univerInstanceService.getCurrentUniverDocInstance(); @@ -43,6 +43,8 @@ export const SwitchDocModeCommand: ICommand = { const unitId = docDataModel.getUnitId(); + const docPageLayoutService = renderManagerService.getRenderById(unitId)?.with(DocPageLayoutService); + const skeleton = renderManagerService.getRenderById(unitId) ?.with(DocSkeletonManagerService) .getSkeleton(); @@ -165,6 +167,8 @@ export const SwitchDocModeCommand: ICommand = { IRichTextEditingMutationParams >(doMutation.id, doMutation.params); + docPageLayoutService?.calculatePagePosition(); + return Boolean(result); }, }; diff --git a/packages/docs-ui/src/commands/commands/table/doc-table-create.command.ts b/packages/docs-ui/src/commands/commands/table/doc-table-create.command.ts index 8fc7fee7dec5..cba6e5d5c37f 100644 --- a/packages/docs-ui/src/commands/commands/table/doc-table-create.command.ts +++ b/packages/docs-ui/src/commands/commands/table/doc-table-create.command.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import type { ICommand, IMutationInfo, JSONXActions } from '@univerjs/core'; +import type { DocumentDataModel, ICommand, IMutationInfo, JSONXActions } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import type { ITextRangeWithStyle } from '@univerjs/engine-render'; -import { CommandType, DataStreamTreeTokenType, ICommandService, IUniverInstanceService, JSONX, TextX, TextXActionType } from '@univerjs/core'; +import { CommandType, DataStreamTreeTokenType, ICommandService, IUniverInstanceService, JSONX, TextX, TextXActionType, UniverInstanceType } from '@univerjs/core'; import { DocSelectionManagerService, RichTextEditingMutation } from '@univerjs/docs'; import { getTextRunAtPosition } from '../../../basics/paragraph'; import { DocMenuStyleService } from '../../../services/doc-menu-style.service'; @@ -51,7 +51,7 @@ export const CreateDocTableCommand: ICommand = { return false; } const { segmentId, segmentPage } = activeRange; - const docDataModel = univerInstanceService.getCurrentUniverDocInstance(); + const docDataModel = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_DOC); const body = docDataModel?.getSelfOrHeaderFooterModel(segmentId).getBody(); if (docDataModel == null || body == null) { return false; @@ -132,12 +132,14 @@ export const CreateDocTableCommand: ICommand = { styleCache ); const { dataStream: tableDataStream, paragraphs: tableParagraphs, sectionBreaks } = genEmptyTable(rowCount, colCount); - const page = curGlyph.parent?.parent?.parent?.parent?.parent; - if (page == null) { + const section = curGlyph.parent?.parent?.parent?.parent; + if (section == null) { return false; } - const { pageWidth, marginLeft, marginRight } = page; - const tableSource = genTableSource(rowCount, colCount, pageWidth - marginLeft - marginRight); + const { columns } = section; + // The new inserted table should have the same column width as the smallest column width in the section. + const minColumnWidth = Math.min(...columns.map((c) => c.width)); + const tableSource = genTableSource(rowCount, colCount, minColumnWidth); textX.push({ t: TextXActionType.INSERT, diff --git a/packages/docs-ui/src/controllers/doc-auto-format.controller.ts b/packages/docs-ui/src/controllers/doc-auto-format.controller.ts index 852936c586e1..bed191484300 100644 --- a/packages/docs-ui/src/controllers/doc-auto-format.controller.ts +++ b/packages/docs-ui/src/controllers/doc-auto-format.controller.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Nullable } from 'vitest'; +import type { Nullable } from '@univerjs/core'; import type { ITabCommandParams } from '../commands/commands/auto-format.command'; import { Disposable, Inject, QuickListTypeMap } from '@univerjs/core'; import { DocSkeletonManagerService } from '@univerjs/docs'; diff --git a/packages/docs-ui/src/controllers/doc-header-footer.controller.ts b/packages/docs-ui/src/controllers/doc-header-footer.controller.ts index 05fc5b35aabb..511295e2360a 100644 --- a/packages/docs-ui/src/controllers/doc-header-footer.controller.ts +++ b/packages/docs-ui/src/controllers/doc-header-footer.controller.ts @@ -14,11 +14,9 @@ * limitations under the License. */ -import type { DocumentDataModel, ICommandInfo } from '@univerjs/core'; +import type { DocumentDataModel, ICommandInfo, Nullable } from '@univerjs/core'; import type { Documents, DocumentViewModel, IMouseEvent, IPageRenderConfig, IPathProps, IPointerEvent, IRenderContext, IRenderModule, RenderComponentType } from '@univerjs/engine-render'; -import type { Nullable } from 'vitest'; -import { BooleanNumber, Disposable, DocumentFlavor, ICommandService, Inject, IUniverInstanceService, LocaleService, toDisposable, Tools } from '@univerjs/core'; - +import { BooleanNumber, Disposable, DocumentFlavor, generateRandomId, ICommandService, Inject, IUniverInstanceService, LocaleService, toDisposable } from '@univerjs/core'; import { DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; import { DocumentEditArea, IRenderManagerService, PageLayoutType, Path, Rect, Vector2 } from '@univerjs/engine-render'; import { ComponentManager } from '@univerjs/ui'; @@ -48,7 +46,11 @@ interface IHeaderFooterCreate { } // TODO: @JOCS also need to check sectionBreak config in the future. -function checkCreateHeaderFooterType(viewModel: DocumentViewModel, editArea: DocumentEditArea, segmentPage: number): IHeaderFooterCreate { +function checkCreateHeaderFooterType( + viewModel: DocumentViewModel, + editArea: DocumentEditArea, + segmentPage: number +): IHeaderFooterCreate { const { documentStyle } = viewModel.getDataModel().getSnapshot(); const { defaultHeaderId, @@ -150,21 +152,6 @@ export class DocHeaderFooterController extends Disposable implements IRenderModu // Close header footer panel when switch mode. private _listenSwitchMode() { - // this.disposeWithMe( - // this._commandService.beforeCommandExecuted((command: ICommandInfo) => { - // if (SwitchDocModeCommand.id === command.id) { - // const viewModel = this._docSkeletonManagerService.getViewModel(); - // const editArea = viewModel.getEditArea(); - - // if (editArea !== DocumentEditArea.BODY) { - // this._commandService.executeCommand(CloseHeaderFooterCommand.id, { - // unitId: this._context.unitId, - // }); - // } - // } - // }) - // ); - this.disposeWithMe( this._commandService.onCommandExecuted((command: ICommandInfo) => { if (RichTextEditingMutation.id === command.id) { @@ -255,7 +242,7 @@ export class DocHeaderFooterController extends Disposable implements IRenderModu } else { if (createType != null) { const SEGMENT_ID_LEN = 6; - const segmentId = Tools.generateRandomId(SEGMENT_ID_LEN); + const segmentId = generateRandomId(SEGMENT_ID_LEN); this._docSelectionRenderService.setSegment(segmentId); this._docSelectionRenderService.setSegmentPage(pageNumber); @@ -327,11 +314,11 @@ export class DocHeaderFooterController extends Disposable implements IRenderModu const editArea = viewModel.getEditArea(); const isEditBody = editArea === DocumentEditArea.BODY; const { page, pageLeft, pageTop, ctx } = config; - const { pageWidth, pageHeight, marginTop, marginBottom } = page; + const { pageWidth, pageHeight, marginTop, marginBottom, left } = page; // Draw header footer label. ctx.save(); - ctx.translate(pageLeft - 0.5, pageTop - 0.5); + ctx.translate(pageLeft - 0.5 + left, pageTop - 0.5); // Cover header and footer. if (isEditBody) { diff --git a/packages/docs-ui/src/services/selection/convert-rect-range.ts b/packages/docs-ui/src/services/selection/convert-rect-range.ts index 3a96e05ab99e..47b35dcf320a 100644 --- a/packages/docs-ui/src/services/selection/convert-rect-range.ts +++ b/packages/docs-ui/src/services/selection/convert-rect-range.ts @@ -225,47 +225,52 @@ export class NodePositionConvertToRectRange { for (let p = skipPageIndex; p < pages.length; p++) { const page = pages[p]; this._liquid.translatePagePadding(page); - const { skeTables } = page; + const { skeTables, left } = page; - let table = null; + const tables = []; for (const [id, tableSke] of skeTables.entries()) { if (id.startsWith(tableId)) { - table = tableSke; + tables.push(tableSke); } } - if (table == null) { + if (tables.length === 0) { this._liquid.restorePagePadding(page); this._liquid.translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); continue; } this._liquid.translateSave(); - this._liquid.translate(0, table.top); - const { x, y } = this._liquid; - const { left: tableLeft } = table; + for (const table of tables) { + this._liquid.translateSave(); + this._liquid.translate(left, table.top); - for (const row of table.rows) { - if (row.index >= startRow && row.index <= endRow) { - const cells = findNonEmptyCellPages(row.cells, startColumn, endColumn); + const { x, y } = this._liquid; + const { left: tableLeft } = table; - if (cells == null) { - continue; - } + for (const row of table.rows) { + if (row.index >= startRow && row.index <= endRow) { + const cells = findNonEmptyCellPages(row.cells, startColumn, endColumn); + + if (cells == null) { + continue; + } - const [rowStartCell, rowEndCell] = cells; + const [rowStartCell, rowEndCell] = cells; - const position = { - startX: x + rowStartCell.left + tableLeft, - startY: y + row.top, - endX: x + rowEndCell.left + rowEndCell.pageWidth + tableLeft, - endY: y + row.top + row.height, - }; + const position = { + startX: x + rowStartCell.left + tableLeft, + startY: y + row.top, + endX: x + rowEndCell.left + rowEndCell.pageWidth + tableLeft, + endY: y + row.top + row.height, + }; - pointGroup.push(pushToPoints(position)); + pointGroup.push(pushToPoints(position)); + } } + this._liquid.translateRestore(); } this._liquid.translateRestore(); diff --git a/packages/docs-ui/src/services/selection/convert-text-range.ts b/packages/docs-ui/src/services/selection/convert-text-range.ts index a4d413e9f2fd..4a866aebdfdd 100644 --- a/packages/docs-ui/src/services/selection/convert-text-range.ts +++ b/packages/docs-ui/src/services/selection/convert-text-range.ts @@ -511,7 +511,11 @@ export class NodePositionConvertToCursor { for (let p = skipPageIndex; p <= endIndex; p++) { const page = pages[p]; - const { headerId, footerId, pageWidth } = page; + const { headerId, footerId, pageWidth, left } = page; + + this._liquid.translateSave(); + this._liquid.translate(left, 0); + let segmentPage: Nullable = page; if (pageType === DocumentSkeletonPageType.HEADER) { @@ -523,6 +527,7 @@ export class NodePositionConvertToCursor { } if (segmentPage == null) { + this._liquid.translateRestore(); this._liquid.translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); continue; } @@ -578,6 +583,7 @@ export class NodePositionConvertToCursor { s ); + this._liquid.translateSave(); this._liquid.translateSection(section); for (let c = start_c; c <= end_c; c++) { @@ -591,6 +597,7 @@ export class NodePositionConvertToCursor { c ); + this._liquid.translateSave(); this._liquid.translateColumn(column); for (let l = start_l; l <= end_l; l++) { @@ -640,8 +647,14 @@ export class NodePositionConvertToCursor { this._liquid.translateRestore(); } + + this._liquid.translateRestore(); } + + this._liquid.translateRestore(); } + + this._liquid.translateRestore(); this._liquid.translateRestore(); this._liquid.translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); diff --git a/packages/docs-ui/src/services/selection/doc-selection-render.service.ts b/packages/docs-ui/src/services/selection/doc-selection-render.service.ts index a3351bf1f55f..f263a1888e73 100644 --- a/packages/docs-ui/src/services/selection/doc-selection-render.service.ts +++ b/packages/docs-ui/src/services/selection/doc-selection-render.service.ts @@ -609,14 +609,14 @@ export class DocSelectionRenderService extends RxDisposable implements IRenderMo } private _setSystemHighlightColorToStyle() { - const { r, g, b, a } = getSystemHighlightColor(); + const { r, g, b } = getSystemHighlightColor(); // Only set selection use highlight color. const style: ITextSelectionStyle = { strokeWidth: 1.5, stroke: 'rgba(0, 0, 0, 0)', strokeActive: 'rgba(0, 0, 0, 1)', - fill: `rgba(${r}, ${g}, ${b}, ${a ?? 0.3})`, + fill: `rgba(${r}, ${g}, ${b}, 0.3)`, }; this._setRangeStyle(style); diff --git a/packages/engine-render/src/basics/const.ts b/packages/engine-render/src/basics/const.ts index 791832329108..4c91f7249662 100644 --- a/packages/engine-render/src/basics/const.ts +++ b/packages/engine-render/src/basics/const.ts @@ -52,7 +52,7 @@ export const DEFAULT_SKELETON_FOOTER = { export const DEFAULT_OFFSET_SPACING = 1; -export const DEFAULT_DOCUMENT_FONTSIZE = 14; +export const DEFAULT_DOCUMENT_FONTSIZE = 11; /** * https://developer.mozilla.org/en-US/docs/Web/CSS/cursor diff --git a/packages/engine-render/src/components/docs/doc-background.ts b/packages/engine-render/src/components/docs/doc-background.ts index 6dafdd074f57..c9d3df0e583f 100644 --- a/packages/engine-render/src/components/docs/doc-background.ts +++ b/packages/engine-render/src/components/docs/doc-background.ts @@ -82,10 +82,10 @@ export class DocBackground extends DocComponent { } // Draw background and margin identifier. - const { width, pageWidth, height, pageHeight, originMarginTop, originMarginBottom, marginLeft, marginRight } = page; + const { width, pageWidth, height, pageHeight, originMarginTop, originMarginBottom, marginLeft, marginRight, left } = page; ctx.save(); - ctx.translate(pageLeft - 0.5, pageTop - 0.5); + ctx.translate(pageLeft - 0.5 + left, pageTop - 0.5); const backgroundOptions = { width: pageWidth ?? width, height: pageHeight ?? height, diff --git a/packages/engine-render/src/components/docs/document.ts b/packages/engine-render/src/components/docs/document.ts index 0d0484bda804..d670e910e773 100644 --- a/packages/engine-render/src/components/docs/document.ts +++ b/packages/engine-render/src/components/docs/document.ts @@ -152,6 +152,7 @@ export class Documents extends DocComponent { footerId, renderConfig = {}, skeTables, + left, } = page; const { verticalAlign = VerticalAlign.TOP, // Do not make changes, otherwise the document will not render. @@ -195,6 +196,9 @@ export class Documents extends DocComponent { continue; } + this._drawLiquid.translateSave(); + this._drawLiquid.translate(left, 0); + if (skeTables.size > 0) { this._drawTable( ctx, @@ -478,6 +482,8 @@ export class Documents extends DocComponent { ctx, }); + this._drawLiquid.translateRestore(); + const { x, y } = this._drawLiquid.translatePage( page, this.pageLayoutType, diff --git a/packages/engine-render/src/components/docs/layout/block/paragraph/layout-ruler.ts b/packages/engine-render/src/components/docs/layout/block/paragraph/layout-ruler.ts index 42991ce14933..3ec132cd142a 100644 --- a/packages/engine-render/src/components/docs/layout/block/paragraph/layout-ruler.ts +++ b/packages/engine-render/src/components/docs/layout/block/paragraph/layout-ruler.ts @@ -30,7 +30,7 @@ import type { IFloatObject, ILayoutContext, } from '../../tools'; -import { BooleanNumber, DataStreamTreeTokenType, GridType, ObjectRelativeFromV, PositionedObjectLayoutType, SpacingRule, TableTextWrapType } from '@univerjs/core'; +import { BooleanNumber, DataStreamTreeTokenType, GridType, ObjectRelativeFromV, PositionedObjectLayoutType, SpacingRule, TableTextWrapType, Tools } from '@univerjs/core'; import { GlyphType, LineType } from '../../../../../basics/i-document-skeleton-cached'; import { BreakPointType } from '../../line-breaker/break'; import { addGlyphToDivide, createSkeletonBulletGlyph } from '../../model/glyph'; @@ -523,7 +523,7 @@ function _lineOperator( const headerPage = skeHeaders?.get(headerId)?.get(pageWidth); const footerPage = skeFooters?.get(footerId)?.get(pageWidth); - let needOpenNewPageByTableLayout = false; + let needOpenNewColumnByTableLayout = false; // Handle float object relative to line. // FIXME: @jocs, it will not update the last line's drawings. @@ -549,18 +549,19 @@ function _lineOperator( } if (skeTablesInParagraph != null && skeTablesInParagraph.length > 0) { - needOpenNewPageByTableLayout = _updateAndPositionTable(ctx, lineTop, lineHeight, lastPage, column, section, skeTablesInParagraph, paragraphConfig.paragraphIndex, sectionBreakConfig, pDrawingAnchor?.get(paragraphIndex)?.top); + needOpenNewColumnByTableLayout = _updateAndPositionTable(ctx, lineTop, lineHeight, lastPage, column, section, skeTablesInParagraph, paragraphConfig.paragraphIndex, sectionBreakConfig, pDrawingAnchor?.get(paragraphIndex)?.top); } const newLineTop = calculateLineTopByDrawings( lineHeight, lineTop, lastPage, + column, headerPage, footerPage ); // WRAP_TOP_AND_BOTTOM 的 drawing 和 WRAP NONE 的 table 会改变行的起始 top - if ((lineHeight + newLineTop > section.height && column.lines.length > 0 && lastPage.sections.length > 0) || needOpenNewPageByTableLayout) { + if ((lineHeight + newLineTop > section.height && column.lines.length > 0 && lastPage.sections.length > 0) || needOpenNewColumnByTableLayout) { // 行高超过Col高度,且列中已存在一行以上,且section大于一个; // console.log('_lineOperator', { glyphGroup, pages, lineHeight, newLineTop, sectionHeight: section.height, lastPage }); setColumnFullState(column, true); @@ -626,7 +627,7 @@ function _lineOperator( marginTop, spaceBelowApply, }, - column.width, + column, lineIndex, isParagraphFirstShapedText, paragraphConfig, @@ -793,13 +794,14 @@ function _updateAndPositionTable( const { tableId, table } = firstUnPositionedTable; const { tableSource } = table; - if (firstUnPositionedTable.isSlideTable === false) { - switch (tableSource.textWrap) { - case TableTextWrapType.NONE: { - table.top = lineTop; - break; - } - case TableTextWrapType.WRAP: { + switch (tableSource.textWrap) { + case TableTextWrapType.NONE: { + table.top = lineTop; + table.left = column.left; + break; + } + case TableTextWrapType.WRAP: { + if (firstUnPositionedTable.isSlideTable === false) { __updateWrapTablePosition( ctx, table, @@ -809,11 +811,11 @@ function _updateAndPositionTable( paragraphIndex, drawingAnchorTop ); - break; - } - default: { - throw new Error(`Unsupported table text wrap type: ${tableSource.textWrap}`); } + break; + } + default: { + throw new Error(`Unsupported table text wrap type: ${tableSource.textWrap}`); } } @@ -1244,19 +1246,20 @@ export function updateInlineDrawingPosition( const { size, angle } = docTransform; const { width = 0, height = 0 } = size; const glyphHeight = glyph.bBox.bd + glyph.bBox.ba; - - drawing.aLeft = divide.left + divide.paddingLeft + glyph.left + 0.5 * glyph.width - 0.5 * width || 0; - drawing.aTop = top + lineHeight - 0.5 * glyphHeight - 0.5 * height - marginBottom; - drawing.width = width; - drawing.height = height; - drawing.angle = angle; - drawing.isPageBreak = isPageBreak; - drawing.lineTop = top; - drawing.columnLeft = column.left; - drawing.blockAnchorTop = blockAnchorTop ?? top; - drawing.lineHeight = line.lineHeight; - - drawings.set(drawing.drawingId, drawing); + const copyDrawing = Tools.deepClone(drawing); + + copyDrawing.aLeft = page.marginLeft + column.left + divide.left + divide.paddingLeft + glyph.left + 0.5 * glyph.width - 0.5 * width || 0; + copyDrawing.aTop = page.marginTop + top + lineHeight - 0.5 * glyphHeight - 0.5 * height - marginBottom; + copyDrawing.width = width; + copyDrawing.height = height; + copyDrawing.angle = angle; + copyDrawing.isPageBreak = isPageBreak; + copyDrawing.lineTop = top; + copyDrawing.columnLeft = column.left; + copyDrawing.blockAnchorTop = blockAnchorTop ?? top; + copyDrawing.lineHeight = line.lineHeight; + + drawings.set(copyDrawing.drawingId, copyDrawing); } } } diff --git a/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts b/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts index 0bf9158ebbe9..272c750a0ee8 100644 --- a/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts +++ b/packages/engine-render/src/components/docs/layout/block/paragraph/shaping.ts @@ -65,11 +65,26 @@ function punctuationSpaceAdjustment(shapedGlyphs: IDocumentSkeletonGlyph[]) { // Add some spacing between Han characters and western characters. // See Requirements for Chinese Text Layout, Section 3.2.2 Mixed Text Composition in Horizontal // Written Mode -function addCJKLatinSpacing(shapedTextList: IShapedText[]) { +function addCJKLatinSpacing( + shapedTextList: IShapedText[], + autoSpaceDE: BooleanNumber = BooleanNumber.TRUE, + autoSpaceDN: BooleanNumber = BooleanNumber.TRUE +) { + if (autoSpaceDE === BooleanNumber.FALSE && autoSpaceDN === BooleanNumber.FALSE) { + return; + } + + let LATIN_REG = /[a-z\d]/i; + + if (autoSpaceDE === BooleanNumber.FALSE) { + LATIN_REG = /[\d]/i; + } else if (autoSpaceDN === BooleanNumber.FALSE) { + LATIN_REG = /[a-z]/i; + } + const shapedGlyphs = shapedTextList.flatMap((shapedText) => shapedText.glyphs); let prevGlyph = null; const len = shapedGlyphs.length; - const LATIN_REG = /[a-z\d]/i; for (let i = 0; i < len; i++) { const curGlyph = shapedGlyphs[i]; @@ -125,13 +140,16 @@ export function shaping( const { endIndex } = paragraphNode; const paragraph = viewModel.getParagraph(endIndex) || { startIndex: 0 }; const { paragraphStyle = {} } = paragraph; - const { snapToGrid = BooleanNumber.TRUE } = paragraphStyle; + const { hyphen, languageDetector, docsConfig } = ctx; + const { + snapToGrid = BooleanNumber.TRUE, + autoSpaceDE = docsConfig.autoSpaceDE, + autoSpaceDN = docsConfig.autoSpaceDN, + } = paragraphStyle; let last = 0; let bk; let lastGlyphIndex = 0; - const { hyphen, languageDetector } = ctx; - const paragraphBody = prepareParagraphBody(viewModel.getBody()!, endIndex); // const now = +new Date(); @@ -339,7 +357,7 @@ export function shaping( const lastShapedGlyphs = shapedGlyphsList[shapedGlyphsList.length - 1]; for (const shapedGlyphs of shapedGlyphsList) { - const word = shapedGlyphs.map((g) => g.content).join(''); + const word = shapedGlyphs.map((g) => g.raw).join(''); shapedTextList.push({ text: word, @@ -352,7 +370,7 @@ export function shaping( } // Add some spacing between Han characters and western characters. - addCJKLatinSpacing(shapedTextList); + addCJKLatinSpacing(shapedTextList, autoSpaceDE, autoSpaceDN); return shapedTextList; } diff --git a/packages/engine-render/src/components/docs/layout/block/section.ts b/packages/engine-render/src/components/docs/layout/block/section.ts index 1308318d9946..84b669569740 100644 --- a/packages/engine-render/src/components/docs/layout/block/section.ts +++ b/packages/engine-render/src/components/docs/layout/block/section.ts @@ -110,6 +110,7 @@ export function dealWithSection( // Roll back pages to the state it was in before the dirty paragraph. function _rollbackPages(paragraphIndex: number, allCurrentSkeletonPages: IDocumentSkeletonPage[]) { let findFirstDirtyLine = false; + let findColumnIndex = -1; for (let pageIndex = 0; pageIndex < allCurrentSkeletonPages.length; pageIndex++) { const page = allCurrentSkeletonPages[pageIndex]; @@ -124,23 +125,21 @@ function _rollbackPages(paragraphIndex: number, allCurrentSkeletonPages: IDocume if (line.paragraphIndex === paragraphIndex) { findFirstDirtyLine = true; + findColumnIndex = columnIndex; column.lines.splice(lineIndex); break; } } if (findFirstDirtyLine) { - let columnSplitIndex = column.lines.length ? columnIndex + 1 : columnIndex; - columnSplitIndex = Math.max(columnSplitIndex, 1); - const preColumnIndex = columnSplitIndex - 1; - if (preColumnIndex >= 0) { - section.columns[preColumnIndex].isFull = false; + if (columnIndex !== findColumnIndex) { + column.lines.length = 0; } - section.columns.splice(columnSplitIndex); - break; + column.isFull = false; } } + // TODO: 目前一个页面只有一个 section,所以还没有问题,后续需要考虑多个 section 的情况。 if (findFirstDirtyLine) { const sectionSplitIndex = sectionIndex + 1; page.sections.splice(sectionSplitIndex); diff --git a/packages/engine-render/src/components/docs/layout/doc-skeleton.ts b/packages/engine-render/src/components/docs/layout/doc-skeleton.ts index 90603ef8b0cf..3511fc23ac10 100644 --- a/packages/engine-render/src/components/docs/layout/doc-skeleton.ts +++ b/packages/engine-render/src/components/docs/layout/doc-skeleton.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { ColumnSeparatorType, ISectionColumnProperties, LocaleService, Nullable } from '@univerjs/core'; +import type { INumberUnit, ISectionColumnProperties, LocaleService, Nullable } from '@univerjs/core'; import type { IDocumentSkeletonCached, IDocumentSkeletonGlyph, @@ -25,7 +25,7 @@ import type { IDocsConfig, INodeInfo, INodePosition, INodeSearch } from '../../. import type { IViewportInfo, Vector2 } from '../../../basics/vector2'; import type { DocumentViewModel } from '../view-model/document-view-model'; import type { ILayoutContext } from './tools'; -import { PRESET_LIST_TYPE, SectionType } from '@univerjs/core'; +import { BooleanNumber, ColumnSeparatorType, PRESET_LIST_TYPE, SectionType } from '@univerjs/core'; import { Subject } from 'rxjs'; import { DocumentSkeletonPageType, GlyphType, LineType, PageLayoutType } from '../../../basics/i-document-skeleton-cached'; import { Skeleton } from '../../skeleton'; @@ -36,7 +36,7 @@ import { Hyphen } from './hyphenation/hyphen'; import { LanguageDetector } from './hyphenation/language-detector'; import { createSkeletonPage } from './model/page'; import { createSkeletonSection } from './model/section'; -import { getLastPage, getNullSkeleton, getPageFromPath, prepareSectionBreakConfig, resetContext, setPageParent, updateBlockIndex, updateInlineDrawingCoords } from './tools'; +import { getLastPage, getNullSkeleton, getPageFromPath, prepareSectionBreakConfig, resetContext, setPageParent, updateBlockIndex, updateInlineDrawingCoords, updatePagesLeft } from './tools'; export enum DocumentSkeletonState { PENDING = 'pending', @@ -448,7 +448,10 @@ export class DocumentSkeleton extends Skeleton { for (let i = 0, len = pages.length; i < len; i++) { const page = pages[i]; - const { marginTop, marginBottom, pageWidth, pageHeight } = page; + const { marginTop, marginBottom, pageWidth, pageHeight, left } = page; + + this._findLiquid.translateSave(); + this._findLiquid.translate(left, 0); if ( x > this._findLiquid.x && x < this._findLiquid.x + pageWidth && @@ -480,6 +483,7 @@ export class DocumentSkeleton extends Skeleton { break; } + this._findLiquid.translateRestore(); this._translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); } @@ -510,6 +514,7 @@ export class DocumentSkeleton extends Skeleton { }; const { pages, skeHeaders, skeFooters } = skeletonData; + const editArea = this.findEditAreaByCoord(coord, pageLayoutType, pageMarginLeft, pageMarginTop).editArea; const pageLength = pages.length; @@ -517,10 +522,13 @@ export class DocumentSkeleton extends Skeleton { if (restrictions == null) { for (let pi = 0; pi < pageLength; pi++) { const page = pages[pi]; - const { headerId, footerId, pageWidth } = page; + const { headerId, footerId, pageWidth, left } = page; let exactMatch = null; + this._findLiquid.translateSave(); + this._findLiquid.translate(left, 0); + if (editArea === DocumentEditArea.HEADER || editArea === DocumentEditArea.FOOTER) { const headerSke = skeHeaders.get(headerId)?.get(pageWidth) as IDocumentSkeletonPage; @@ -568,6 +576,8 @@ export class DocumentSkeleton extends Skeleton { ); } + this._findLiquid.translateRestore(); + if (exactMatch) { return exactMatch; } @@ -581,11 +591,13 @@ export class DocumentSkeleton extends Skeleton { if (strict === false) { for (let pi = 0; pi < pageLength; pi++) { const page = pages[pi]; - const { headerId, footerId, pageWidth } = page; + const { headerId, footerId, pageWidth, left } = page; + + this._findLiquid.translateSave(); + this._findLiquid.translate(left, 0); if (segmentId !== '') { const headerSke = skeHeaders.get(headerId)?.get(pageWidth) as IDocumentSkeletonPage; - if (headerSke) { exactMatch = this._collectNearestNode( headerSke, @@ -630,6 +642,8 @@ export class DocumentSkeleton extends Skeleton { ); } + this._findLiquid.translateRestore(); + if (exactMatch) { return exactMatch; } @@ -639,9 +653,14 @@ export class DocumentSkeleton extends Skeleton { } else { for (let pi = 0; pi < pageLength; pi++) { const page = pages[pi]; + const { left } = page; + + this._findLiquid.translateSave(); + this._findLiquid.translate(left, 0); if (segmentId) { if (segmentPage !== pi) { + this._findLiquid.translateRestore(); this._translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); continue; } @@ -677,6 +696,8 @@ export class DocumentSkeleton extends Skeleton { ); } + this._findLiquid.translateRestore(); + if (exactMatch) { return exactMatch; } @@ -1016,6 +1037,8 @@ export class DocumentSkeleton extends Skeleton { paragraphLineGapDefault = 0, defaultTabStop = 10.5, textStyle = {}, + autoSpaceDE = BooleanNumber.TRUE, + autoSpaceDN = BooleanNumber.TRUE, } = documentStyle; const docsConfig: IDocsConfig = { @@ -1023,11 +1046,12 @@ export class DocumentSkeleton extends Skeleton { footerTreeMap, lists, drawings, - localeService: this._localeService, paragraphLineGapDefault, defaultTabStop, documentTextStyle: textStyle, + autoSpaceDE, + autoSpaceDN, }; const skeleton = getNullSkeleton(); @@ -1116,23 +1140,43 @@ export class DocumentSkeleton extends Skeleton { for (let i = startSectionIndex, len = viewModel.getChildren().length; i < len; i++) { const sectionNode = viewModel.getChildren()[i]; const sectionBreakConfig = prepareSectionBreakConfig(ctx, i); - const { sectionType, columnProperties, columnSeparatorType, sectionTypeNext, pageNumberStart = 1 } = sectionBreakConfig; + const { + sectionType, + columnProperties = [], + columnSeparatorType = ColumnSeparatorType.COLUMN_SEPARATOR_STYLE_UNSPECIFIED, + sectionTypeNext, + pageNumberStart = 1, + equalWidth = BooleanNumber.TRUE, + numOfEqualWidthColumns = 1, + spaceBetweenEqualWidthColumns = { v: 10 }, + } = sectionBreakConfig; let curSkeletonPage = getLastPage(allSkeletonPages); let isContinuous = false; ctx.sectionBreakConfigCache.set(sectionNode.endIndex, sectionBreakConfig); - if (sectionType === SectionType.CONTINUOUS) { + if (sectionType === SectionType.CONTINUOUS && curSkeletonPage) { updateBlockIndex(allSkeletonPages); - this._addNewSectionByContinuous(curSkeletonPage, columnProperties!, columnSeparatorType!); + this._addNewSectionByContinuous( + curSkeletonPage, + columnProperties, + columnSeparatorType, + equalWidth, + numOfEqualWidthColumns, + spaceBetweenEqualWidthColumns + ); isContinuous = true; - } else if (layoutAnchor == null || curSkeletonPage == null) { + } else if ( + layoutAnchor == null || + curSkeletonPage == null || + (sectionType === SectionType.NEXT_PAGE && layoutAnchor && startSectionIndex !== i) + ) { curSkeletonPage = createSkeletonPage( ctx, sectionBreakConfig, skeletonResourceReference, - curSkeletonPage?.pageNumber ?? pageNumberStart + curSkeletonPage ? curSkeletonPage.pageNumber + 1 : pageNumberStart ); } @@ -1174,8 +1218,11 @@ export class DocumentSkeleton extends Skeleton { this._iteratorCount = 0; removeDupPages(ctx); updateBlockIndex(skeleton.pages); + updatePagesLeft(skeleton.pages); // Calculate inline drawing position and update. updateInlineDrawingCoords(ctx, skeleton.pages); + + // Update the position of inline drawing in header and footer. for (const hSkeMap of skeleton.skeHeaders.values()) { for (const page of hSkeMap.values()) { updateInlineDrawingCoords(ctx, [page]); @@ -1186,6 +1233,7 @@ export class DocumentSkeleton extends Skeleton { updateInlineDrawingCoords(ctx, [page]); } } + setPageParent(skeleton.pages, skeleton); return skeleton; @@ -1196,7 +1244,10 @@ export class DocumentSkeleton extends Skeleton { private _addNewSectionByContinuous( curSkeletonPage: IDocumentSkeletonPage, columnProperties: ISectionColumnProperties[], - columnSeparatorType: ColumnSeparatorType + columnSeparatorType: ColumnSeparatorType, + equalWidth: BooleanNumber, + numOfEqualWidthColumns: number, + spaceBetweenEqualWidthColumns: INumberUnit ) { const sections = curSkeletonPage.sections; const lastSection = sections[sections.length - 1]; @@ -1212,6 +1263,9 @@ export class DocumentSkeleton extends Skeleton { const pageContentHeight = pageHeight - curPageMT - curPageMB; const lastSectionBottom = (lastSection?.top || 0) + (lastSection?.height || 0); const newSection = createSkeletonSection( + equalWidth, + numOfEqualWidthColumns, + spaceBetweenEqualWidthColumns, columnProperties, columnSeparatorType, lastSectionBottom, diff --git a/packages/engine-render/src/components/docs/layout/model/column.ts b/packages/engine-render/src/components/docs/layout/model/column.ts index 1752b36d6e52..9d12af4456b3 100644 --- a/packages/engine-render/src/components/docs/layout/model/column.ts +++ b/packages/engine-render/src/components/docs/layout/model/column.ts @@ -15,11 +15,11 @@ */ import type { ISectionColumnProperties } from '@univerjs/core'; -import { ColumnSeparatorType } from '@univerjs/core'; - import type { IDocumentSkeletonColumn } from '../../../../basics/i-document-skeleton-cached'; -export function createSkeletonColumn( +import { ColumnSeparatorType } from '@univerjs/core'; + +function createSkeletonColumn( columnIndex: number = 0, columnProperties: ISectionColumnProperties[], columnSeparatorType: ColumnSeparatorType = ColumnSeparatorType.NONE, diff --git a/packages/engine-render/src/components/docs/layout/model/line.ts b/packages/engine-render/src/components/docs/layout/model/line.ts index bb6e27053fc5..6a64015667f9 100644 --- a/packages/engine-render/src/components/docs/layout/model/line.ts +++ b/packages/engine-render/src/components/docs/layout/model/line.ts @@ -18,6 +18,7 @@ import type { IDistFromText, Nullable } from '@univerjs/core'; import type { IParagraphConfig } from '../../../../basics'; import type { + IDocumentSkeletonColumn, IDocumentSkeletonDivide, IDocumentSkeletonDrawing, IDocumentSkeletonDrawingAnchor, @@ -65,7 +66,7 @@ export function createSkeletonLine( paragraphIndex: number, lineType: LineType, lineBoundingBox: ILineBoundingBox, - columnWidth: number, + column: IDocumentSkeletonColumn, lineIndex: number = 0, isParagraphStart: boolean = false, paragraphConfig: IParagraphConfig, @@ -112,10 +113,10 @@ export function createSkeletonLine( const affectSkeDrawings = new Map(Array.from(pageSkeDrawings).filter(([_, drawing]) => drawing.drawingOrigin.layoutType !== PositionedObjectLayoutType.INLINE)); const wrapTypeTables = new Map(Array.from(pageSkeTables).filter(([_, table]) => table.tableSource.textWrap === TableTextWrapType.WRAP)); - lineSke.divides = _calculateDividesByDrawings( + lineSke.divides = _calculateDividesByFloatObjects( lineHeight, lineTop, - columnWidth, + column, paddingLeft, paddingRight, page, @@ -138,6 +139,7 @@ export function calculateLineTopByDrawings( lineHeight: number = 15.6, lineTop: number = 0, page: IDocumentSkeletonPage, + column: IDocumentSkeletonColumn, headerPage: Nullable, footerPage: Nullable ) { @@ -150,7 +152,7 @@ export function calculateLineTopByDrawings( if (headerPage && headersDrawings) { headersDrawings.forEach((drawing) => { const transformedDrawing = translateHeaderFooterDrawingPosition(drawing, headerPage, page, true); - const top = _getLineTopWidthWrapTopBottom(transformedDrawing, lineHeight, lineTop); + const top = _getLineTopWidthWrapTopBottom(transformedDrawing, lineHeight, lineTop, column, page); if (top) { maxTop = Math.max(maxTop, top); } @@ -160,7 +162,7 @@ export function calculateLineTopByDrawings( if (footerPage && footersDrawings) { footersDrawings.forEach((drawing) => { const transformedDrawing = translateHeaderFooterDrawingPosition(drawing, footerPage, page, false); - const top = _getLineTopWidthWrapTopBottom(transformedDrawing, lineHeight, lineTop); + const top = _getLineTopWidthWrapTopBottom(transformedDrawing, lineHeight, lineTop, column, page); if (top) { maxTop = Math.max(maxTop, top); } @@ -168,14 +170,14 @@ export function calculateLineTopByDrawings( } pageSkeDrawings?.forEach((drawing) => { - const top = _getLineTopWidthWrapTopBottom(drawing, lineHeight, lineTop); + const top = _getLineTopWidthWrapTopBottom(drawing, lineHeight, lineTop, column, page); if (top) { maxTop = Math.max(maxTop, top); } }); skeNonWrapTables?.forEach((table) => { - const top = _getLineTopWidthWrapNone(table, lineHeight, lineTop); + const top = _getLineTopWidthWrapNone(table, lineHeight, lineTop, column); if (top) { maxTop = Math.max(maxTop, top); } @@ -184,20 +186,34 @@ export function calculateLineTopByDrawings( return maxTop; } -function _getLineTopWidthWrapNone(table: IDocumentSkeletonTable, lineHeight: number, lineTop: number) { - const { top, height } = table; +function _getLineTopWidthWrapNone( + table: IDocumentSkeletonTable, + lineHeight: number, + lineTop: number, + column: IDocumentSkeletonColumn +) { + const { top, height, left, width } = table; + const { left: colLeft, width: colWidth } = column; // No need to consider the dist. - if (top + height < lineTop || top > lineHeight + lineTop) { + if (top + height < lineTop || top > lineHeight + lineTop || colLeft + colWidth < left || colLeft > left + width) { return; } return top + height; } -function _getLineTopWidthWrapTopBottom(drawing: IDocumentSkeletonDrawing, lineHeight: number, lineTop: number) { +function _getLineTopWidthWrapTopBottom( + drawing: IDocumentSkeletonDrawing, + lineHeight: number, + lineTop: number, + column: IDocumentSkeletonColumn, + page: IDocumentSkeletonPage +) { const { aTop, height, aLeft, width, angle = 0, drawingOrigin } = drawing; - const { layoutType, distT = 0, distB = 0 } = drawingOrigin; + const { layoutType, distT = 0, distB = 0, distL = 0, distR = 0 } = drawingOrigin; + const { left: colLeft, width: colWidth } = column; + const { marginTop, marginLeft } = page; if (layoutType !== PositionedObjectLayoutType.WRAP_TOP_AND_BOTTOM) { return; @@ -210,31 +226,35 @@ function _getLineTopWidthWrapTopBottom(drawing: IDocumentSkeletonDrawing, lineHe // } if (angle === 0) { - const newAtop = aTop - distT; + const newAtop = aTop - distT - marginTop; const newHeight = distT + height + distB; + const newALeft = aLeft - distL - marginLeft; + const newWidth = distL + width + distR; - if (newAtop + newHeight < lineTop || newAtop > lineHeight + lineTop) { + if (newAtop + newHeight < lineTop || newAtop > lineHeight + lineTop || colLeft + colWidth < newALeft || colLeft > newALeft + newWidth) { return; } return newAtop + newHeight; } - // 旋转的情况,要考虑行首位与drawing旋转后得到的最大区域 - let { top: sTop = 0, height: sHeight = 0 } = getBoundingBox(angle, aLeft, width, aTop, height); + // 旋转的情况,要考虑行首位与 drawing 旋转后得到的最大区域 + let { top: sTop = 0, height: sHeight = 0, width: sWidth = 0, left: sLeft = 0 } = getBoundingBox(angle, aLeft, width, aTop, height); - sTop -= distT; - sHeight += distB; + sTop = sTop - distT - marginTop; + sHeight += distT + distB; + sLeft = sLeft - distL - marginLeft; + sWidth += distL + distR; - if (sTop + sHeight < lineTop || sTop > lineHeight + lineTop) { + if (sTop + sHeight < lineTop || sTop > lineHeight + lineTop || colLeft + colWidth < sLeft || colLeft > sLeft + sWidth) { return; } return sTop + sHeight; } -function _calculateDividesByDrawings( +function _calculateDividesByFloatObjects( lineHeight: number, lineTop: number, - columnWidth: number, + column: IDocumentSkeletonColumn, paddingLeft: number, paddingRight: number, page: IDocumentSkeletonPage, @@ -253,7 +273,7 @@ function _calculateDividesByDrawings( width: paddingLeft, }, { - left: columnWidth - paddingRight, + left: column.width - paddingRight, width: paddingRight, } ); @@ -261,7 +281,7 @@ function _calculateDividesByDrawings( if (headerPage && headersDrawings) { headersDrawings.forEach((drawing) => { const transformedDrawing = translateHeaderFooterDrawingPosition(drawing, headerPage, page, true); - const split = _calculateSplit(transformedDrawing, lineHeight, lineTop, columnWidth); + const split = _calculateSplit(transformedDrawing, lineHeight, lineTop, column); if (split) { drawingsMix.push(split); @@ -272,7 +292,7 @@ function _calculateDividesByDrawings( if (footerPage && footersDrawings) { footersDrawings.forEach((drawing) => { const transformedDrawing = translateHeaderFooterDrawingPosition(drawing, footerPage, page, false); - const split = _calculateSplit(transformedDrawing, lineHeight, lineTop, columnWidth); + const split = _calculateSplit(transformedDrawing, lineHeight, lineTop, column); if (split) { drawingsMix.push(split); @@ -281,7 +301,7 @@ function _calculateDividesByDrawings( } paragraphNonInlineSkeDrawings?.forEach((drawing) => { - const split = _calculateSplit(drawing, lineHeight, lineTop, columnWidth); + const split = _calculateSplit(drawing, lineHeight, lineTop, column); if (split) { drawingsMix.push(split); @@ -292,7 +312,7 @@ function _calculateDividesByDrawings( wrapTypeTables.forEach((table) => { const { left, top, width, height, tableSource } = table; const { dist } = tableSource; - const split = __getSplitWidthNoAngle(top, height, left, width, lineTop, lineHeight, columnWidth, dist); + const split = __getSplitWidthNoAngle(top, height, left, width, lineTop, lineHeight, column, dist); if (split) { drawingsMix.push(split); @@ -300,7 +320,7 @@ function _calculateDividesByDrawings( }); } - return _calculateDivideByDrawings(columnWidth, drawingsMix); + return _calculateDivideByDrawings(column.width, drawingsMix); } export function updateDivideInfo(divide: IDocumentSkeletonDivide, states: Partial) { @@ -337,10 +357,11 @@ function _calculateSplit( drawing: IDocumentSkeletonDrawing, lineHeight: number, lineTop: number, - columnWidth: number + column: IDocumentSkeletonColumn ): Nullable { - const { aTop, height, aLeft, width, angle = 0, drawingOrigin } = drawing; + const { aTop, aLeft, height, width, angle = 0, drawingOrigin } = drawing; const { layoutType } = drawingOrigin; + const columnWidth = column.width; if ( layoutType === PositionedObjectLayoutType.WRAP_NONE || @@ -378,7 +399,7 @@ function _calculateSplit( if (angle === 0) { // 无旋转的情况, wrapSquare | wrapThrough | wrapTight - return __getSplitWidthNoAngle(aTop, height, aLeft, width, lineTop, lineHeight, columnWidth, dist, layoutType, wrapText); + return __getSplitWidthNoAngle(aTop, height, aLeft, width, lineTop, lineHeight, column, dist, layoutType, wrapText); } // 旋转的情况,要考虑行首位与drawing旋转后得到的最大区域 @@ -394,7 +415,7 @@ function _calculateSplit( sWidth!, lineTop, lineHeight, - columnWidth, + column, dist, layoutType, wrapText @@ -490,26 +511,39 @@ function __getSplitWidthNoAngle( width: number, lineTop: number, lineHeight: number, - columnWidth: number, + column: IDocumentSkeletonColumn, dist: IDistFromText, layoutType: PositionedObjectLayoutType = PositionedObjectLayoutType.WRAP_SQUARE, wrapText: WrapTextType = WrapTextType.BOTH_SIDES ) { + const page = column.parent?.parent; + + if (page == null) { + return; + } + const { marginTop, marginLeft } = page; const { distL = 0, distR = 0, distT = 0, distB = 0, } = dist; - - const newAtop = top - (layoutType === PositionedObjectLayoutType.WRAP_SQUARE ? distT : 0); + const columnWidth = column.width; + const newAtop = top - marginTop - (layoutType === PositionedObjectLayoutType.WRAP_SQUARE ? distT : 0); const newHeight = height + (layoutType === PositionedObjectLayoutType.WRAP_SQUARE ? distB + distT : 0); + const newALeft = left - marginLeft - (layoutType === PositionedObjectLayoutType.WRAP_SQUARE ? distL : 0); + const newWidth = width + (layoutType === PositionedObjectLayoutType.WRAP_SQUARE ? distL + distR : 0); - if (newAtop + newHeight <= lineTop || newAtop >= lineHeight + lineTop) { + if ( + newAtop + newHeight <= lineTop || + newAtop >= lineHeight + lineTop || + newALeft + newWidth < column.left || + column.left + columnWidth < newALeft + ) { return; } - let resultLeft = left - distL; + let resultLeft = left - marginLeft - distL - column.left; let resultWidth = width + distL + distR; const ruler = ___getWrapTextRuler(wrapText, resultLeft, resultWidth, columnWidth); @@ -517,7 +551,7 @@ function __getSplitWidthNoAngle( resultWidth = columnWidth - resultLeft; } else if (ruler === WrapTextRuler.RIGHT) { resultLeft = 0; - resultWidth = left + width + distR; + resultWidth = left - marginLeft + width + distR - column.left; } return { diff --git a/packages/engine-render/src/components/docs/layout/model/page.ts b/packages/engine-render/src/components/docs/layout/model/page.ts index 5059d8ec9157..bdbb6a8f771b 100644 --- a/packages/engine-render/src/components/docs/layout/model/page.ts +++ b/packages/engine-render/src/components/docs/layout/model/page.ts @@ -24,7 +24,7 @@ import type { ISectionBreakConfig } from '../../../../basics/interfaces'; import type { DataStreamTreeNode } from '../../view-model/data-stream-tree-node'; import type { DocumentViewModel } from '../../view-model/document-view-model'; import type { ILayoutContext } from '../tools'; -import { BooleanNumber, PageOrientType } from '@univerjs/core'; +import { BooleanNumber, ColumnSeparatorType, PageOrientType } from '@univerjs/core'; import { BreakType, DocumentSkeletonPageType } from '../../../../basics/i-document-skeleton-cached'; import { dealWithSection } from '../block/section'; import { resetContext, updateBlockIndex } from '../tools'; @@ -56,7 +56,7 @@ export function createSkeletonPage( footerTreeMap, headerTreeMap, columnProperties = [], - columnSeparatorType, + columnSeparatorType = ColumnSeparatorType.COLUMN_SEPARATOR_STYLE_UNSPECIFIED, marginTop = 0, marginBottom = 0, marginHeader: _marginHeader = 0, @@ -64,11 +64,18 @@ export function createSkeletonPage( marginLeft = 0, marginRight = 0, renderConfig = {}, + equalWidth = BooleanNumber.TRUE, + numOfEqualWidthColumns = 1, + spaceBetweenEqualWidthColumns = { v: 10 }, } = sectionBreakConfig; const { skeHeaders, skeFooters } = skeletonResourceReference; - const { width: pageWidth = Number.POSITIVE_INFINITY, height: pageHeight = Number.POSITIVE_INFINITY } = pageSize; + let { width: pageWidth = Number.POSITIVE_INFINITY, height: pageHeight = Number.POSITIVE_INFINITY } = pageSize; + + if (pageOrient === PageOrientType.LANDSCAPE) { + [pageWidth, pageHeight] = [pageHeight, pageWidth]; + } page.pageNumber = pageNumber; page.pageNumberStart = pageNumberStart; @@ -87,6 +94,7 @@ export function createSkeletonPage( let headerId = defaultHeaderId ?? ''; let footerId = defaultFooterId ?? ''; + if (pageNumber === pageNumberStart && useFirstPageHeaderFooter === BooleanNumber.TRUE) { headerId = firstPageHeaderId ?? ''; footerId = firstPageFooterId ?? ''; @@ -110,7 +118,13 @@ export function createSkeletonPage( true ); - skeHeaders.set(headerId, new Map([[pageWidth, header]])); + let headerMap = skeHeaders.get(headerId); + if (headerMap == null) { + headerMap = new Map(); + skeHeaders.set(headerId, headerMap); + } + + headerMap.set(pageWidth, header); } page.headerId = headerId; } @@ -128,7 +142,13 @@ export function createSkeletonPage( false ); - skeFooters.set(footerId, new Map([[pageWidth, footer]])); + let footerMap = skeFooters.get(footerId); + if (footerMap == null) { + footerMap = new Map(); + skeFooters.set(footerId, footerMap); + } + + footerMap.set(pageWidth, footer); } page.footerId = footerId; } @@ -149,6 +169,9 @@ export function createSkeletonPage( } const newSection = createSkeletonSection( + equalWidth, + numOfEqualWidthColumns, + spaceBetweenEqualWidthColumns, columnProperties, columnSeparatorType, lastSectionBottom, @@ -212,17 +235,25 @@ function _createSkeletonHeaderFooter( lists, footerTreeMap, headerTreeMap, localeService, pageSize, drawings, marginLeft = 0, marginRight = 0, marginHeader = 0, marginFooter = 0, + pageOrient = PageOrientType.PORTRAIT, } = sectionBreakConfig; - const pageWidth = pageSize?.width || Number.POSITIVE_INFINITY; - const pageHeight = pageSize?.height || Number.POSITIVE_INFINITY; + let pageWidth = pageSize?.width || Number.POSITIVE_INFINITY; + let pageHeight = pageSize?.height || Number.POSITIVE_INFINITY; + + if (pageOrient === PageOrientType.LANDSCAPE) { + [pageWidth, pageHeight] = [pageHeight, pageWidth]; + } + const headerFooterConfig: ISectionBreakConfig = { lists, footerTreeMap, headerTreeMap, pageSize: { - width: pageWidth - marginLeft - marginRight, + width: pageWidth, height: getHeaderFooterMaxHeight(pageHeight) - (isHeader ? marginHeader : marginFooter) - 5, }, + marginLeft, + marginRight, localeService, drawings, }; diff --git a/packages/engine-render/src/components/docs/layout/model/section.ts b/packages/engine-render/src/components/docs/layout/model/section.ts index a25c79f9b021..1e761be4d6cc 100644 --- a/packages/engine-render/src/components/docs/layout/model/section.ts +++ b/packages/engine-render/src/components/docs/layout/model/section.ts @@ -14,12 +14,14 @@ * limitations under the License. */ -import type { ISectionColumnProperties } from '@univerjs/core'; +import type { INumberUnit, ISectionColumnProperties } from '@univerjs/core'; import type { IDocumentSkeletonColumn, IDocumentSkeletonSection } from '../../../../basics/i-document-skeleton-cached'; - -import { ColumnSeparatorType } from '@univerjs/core'; +import { BooleanNumber, ColumnSeparatorType } from '@univerjs/core'; export function createSkeletonSection( + equalWidth: BooleanNumber = BooleanNumber.TRUE, + numOfEqualWidthColumns: number = 1, + spaceBetweenEqualWidthColumns: INumberUnit = { v: 10 }, columnProperties: ISectionColumnProperties[] = [], columnSeparatorType: ColumnSeparatorType = ColumnSeparatorType.NONE, top: number = 0, @@ -30,30 +32,40 @@ export function createSkeletonSection( const columns: IDocumentSkeletonColumn[] = []; let colWidth = 0; let spaceWidth = 0; + let leftOffset = left; + + if (equalWidth === BooleanNumber.TRUE) { + colWidth = sectionWidth / numOfEqualWidthColumns; - if (columnProperties.length === 0) { - columns.push(_getSkeletonColumn(left, sectionWidth, 0, ColumnSeparatorType.NONE)); + for (let i = 0; i < numOfEqualWidthColumns; i++) { + spaceWidth = i === numOfEqualWidthColumns - 1 ? 0 : spaceBetweenEqualWidthColumns.v; + columns.push(_getSkeletonColumn(leftOffset, colWidth, spaceWidth, columnSeparatorType)); + leftOffset += colWidth + spaceWidth; + } } else { - for (let i = 0; i < columnProperties.length; i++) { - const { width, paddingEnd } = columnProperties[i]; + if (columnProperties.length <= 1) { + columns.push(_getSkeletonColumn(leftOffset, sectionWidth, 0, ColumnSeparatorType.NONE)); + } else { + for (let i = 0; i < columnProperties.length; i++) { + const { width, paddingEnd } = columnProperties[i]; - spaceWidth = paddingEnd; - colWidth = width; + spaceWidth = paddingEnd; + colWidth = width; - columns.push(_getSkeletonColumn(left, colWidth, spaceWidth, columnSeparatorType)); + if (i === columnProperties.length - 1) { + spaceWidth = 0; + } - left += colWidth + spaceWidth; + columns.push(_getSkeletonColumn(leftOffset, colWidth, spaceWidth, columnSeparatorType)); - if (i === columnProperties.length - 1) { - colWidth = sectionWidth !== Number.POSITIVE_INFINITY ? sectionWidth - colWidth : width; - spaceWidth = 0; - columns.push(_getSkeletonColumn(left, colWidth, spaceWidth, columnSeparatorType)); + leftOffset += colWidth + spaceWidth; } } } + const newSection = { columns, - colCount: columnProperties?.length || 1, + colCount: columns.length, height: sectionHeight, top, st: 0, diff --git a/packages/engine-render/src/components/docs/layout/shaping-engine/text-shaping.ts b/packages/engine-render/src/components/docs/layout/shaping-engine/text-shaping.ts index 235bc8cbb593..2ec28b60f785 100644 --- a/packages/engine-render/src/components/docs/layout/shaping-engine/text-shaping.ts +++ b/packages/engine-render/src/components/docs/layout/shaping-engine/text-shaping.ts @@ -14,10 +14,10 @@ * limitations under the License. */ +import type Opentype from 'opentype.js'; import { BooleanNumber, type IDocumentBody, type IStyleBase, type Nullable } from '@univerjs/core'; // @ts-ignore import { parse } from 'opentype.js/dist/opentype.module'; -import type Opentype from 'opentype.js'; import { DEFAULT_FONTFACE_PLANE } from '../../../../basics/const'; import { EMOJI_REG } from '../../../../basics/tools'; import { fontLibrary } from './font-library'; diff --git a/packages/engine-render/src/components/docs/layout/tools.ts b/packages/engine-render/src/components/docs/layout/tools.ts index 9a9cacfac16f..6663a834614c 100644 --- a/packages/engine-render/src/components/docs/layout/tools.ts +++ b/packages/engine-render/src/components/docs/layout/tools.ts @@ -342,9 +342,9 @@ export function updateBlockIndex(pages: IDocumentSkeletonPage[], start: number = for (const column of columns) { const { lines } = column; - const columStartIndex = preColumnStartIndex; - const columnEndIndex = columStartIndex; - let preLineStartIndex = columStartIndex; + const columnStartIndex = preColumnStartIndex; + const columnEndIndex = columnStartIndex; + let preLineStartIndex = columnStartIndex; let columnHeight = 0; let maxColumnWidth = Number.NEGATIVE_INFINITY; // const preLine: Nullable = null; @@ -429,7 +429,7 @@ export function updateBlockIndex(pages: IDocumentSkeletonPage[], start: number = // because of float objects will between lines. preLineStartIndex = line.ed; } - column.st = columStartIndex + 1; + column.st = columnStartIndex + 1; column.ed = preLineStartIndex >= column.st ? preLineStartIndex : column.st; column.height = columnHeight; @@ -467,6 +467,18 @@ export function updateBlockIndex(pages: IDocumentSkeletonPage[], start: number = } } +export function updatePagesLeft(pages: IDocumentSkeletonPage[]) { + const maxPageWidth = Math.max(...pages.map((page) => page.pageWidth)); + + if (!Number.isFinite(maxPageWidth) || Number.isNaN(maxPageWidth)) { + return; + } + + for (const page of pages) { + page.left = (maxPageWidth - page.pageWidth) / 2; + } +} + export function updateInlineDrawingCoords(ctx: ILayoutContext, pages: IDocumentSkeletonPage[]) { lineIterator(pages, (line, _, __, page) => { const { segmentId } = page; @@ -621,7 +633,7 @@ export function getPositionHorizon( let absoluteLeft = 0; if (relativeFrom === ObjectRelativeFromH.COLUMN) { - absoluteLeft = (isPageBreak ? 0 : column?.left || 0) + posOffset; + absoluteLeft = marginLeft + (isPageBreak ? 0 : column?.left || 0) + posOffset; } else if (relativeFrom === ObjectRelativeFromH.LEFT_MARGIN) { // TODO } else if (relativeFrom === ObjectRelativeFromH.MARGIN) { @@ -705,11 +717,11 @@ export function getPositionVertical( const { marginTop } = page; if (relativeFrom === ObjectRelativeFromV.LINE) { - absoluteTop = (lineTop || 0) + posOffset; + absoluteTop = marginTop + (lineTop || 0) + posOffset; } else if (relativeFrom === ObjectRelativeFromV.TOP_MARGIN) { // TODO } else if (relativeFrom === ObjectRelativeFromV.MARGIN) { - absoluteTop = posOffset; + absoluteTop = posOffset + marginTop; } else if (relativeFrom === ObjectRelativeFromV.BOTTOM_MARGIN) { // TODO } else if (relativeFrom === ObjectRelativeFromV.INSIDE_MARGIN) { @@ -717,9 +729,9 @@ export function getPositionVertical( } else if (relativeFrom === ObjectRelativeFromV.OUTSIDE_MARGIN) { // TODO } else if (relativeFrom === ObjectRelativeFromV.PAGE) { - absoluteTop = posOffset - marginTop; + absoluteTop = posOffset; } else if (relativeFrom === ObjectRelativeFromV.PARAGRAPH) { - absoluteTop = (isPageBreak ? 0 : blockAnchorTop == null ? lineTop : blockAnchorTop) + posOffset; + absoluteTop = marginTop + (isPageBreak ? 0 : blockAnchorTop == null ? lineTop : blockAnchorTop) + posOffset; } return absoluteTop; } else if (percent != null) { @@ -973,6 +985,7 @@ const DEFAULT_MODERN_DOCUMENT_STYLE: IDocumentStyle = { width: ptToPixel(595), height: Number.POSITIVE_INFINITY, }, + pageOrient: PageOrientType.PORTRAIT, marginTop: ptToPixel(50), marginBottom: ptToPixel(50), marginRight: ptToPixel(50), @@ -999,7 +1012,17 @@ const DEFAULT_MODERN_DOCUMENT_STYLE: IDocumentStyle = { const DEFAULT_MODERN_SECTION_BREAK: Partial = { columnProperties: [], columnSeparatorType: ColumnSeparatorType.NONE, - sectionType: SectionType.SECTION_TYPE_UNSPECIFIED, + equalWidth: BooleanNumber.FALSE, + sectionType: SectionType.CONTINUOUS, + pageSize: { + width: ptToPixel(595), + height: Number.POSITIVE_INFINITY, + }, + pageOrient: PageOrientType.PORTRAIT, + marginTop: ptToPixel(50), + marginBottom: ptToPixel(50), + marginRight: ptToPixel(50), + marginLeft: ptToPixel(50), }; export function prepareSectionBreakConfig(ctx: ILayoutContext, nodeIndex: number) { @@ -1073,6 +1096,9 @@ export function prepareSectionBreakConfig(ctx: ILayoutContext, nodeIndex: number useFirstPageHeaderFooter = global_useFirstPageHeaderFooter, evenAndOddHeaders = global_evenAndOddHeaders, + equalWidth = BooleanNumber.TRUE, + numOfEqualWidthColumns = 1, + spaceBetweenEqualWidthColumns = { v: 10 }, columnProperties = [], columnSeparatorType = ColumnSeparatorType.NONE, contentDirection, @@ -1123,6 +1149,9 @@ export function prepareSectionBreakConfig(ctx: ILayoutContext, nodeIndex: number sectionTypeNext, textDirection, renderConfig, + equalWidth, + numOfEqualWidthColumns, + spaceBetweenEqualWidthColumns, autoHyphenation, doNotHyphenateCaps, diff --git a/packages/engine-render/src/components/docs/liquid.ts b/packages/engine-render/src/components/docs/liquid.ts index 8050cb22109a..02f45ba41c9c 100644 --- a/packages/engine-render/src/components/docs/liquid.ts +++ b/packages/engine-render/src/components/docs/liquid.ts @@ -94,23 +94,12 @@ export class Liquid { translatePage( page: IDocumentSkeletonPage, type = PageLayoutType.VERTICAL, - left = 0, - top = 0, - _right = 0, - _bottom = 0 + docLeft = 0, + docTop = 0 ) { const { - // sections, - // marginTop: pagePaddingTop = 0, - // marginBottom: pagePaddingBottom = 0, - // marginLeft: pagePaddingLeft = 0, - // marginRight: pagePaddingRight = 0, pageWidth, pageHeight, - // width, - // height, - // pageNumber = 1, - // renderConfig = {}, } = page; let pageTop = 0; @@ -118,9 +107,9 @@ export class Liquid { let pageLeft = 0; if (type === PageLayoutType.VERTICAL) { - pageTop += pageHeight + top; + pageTop += pageHeight + docTop; } else if (type === PageLayoutType.HORIZONTAL) { - pageLeft += pageWidth + left; + pageLeft += pageWidth + docLeft; } this.translate(pageLeft, pageTop);