我有这个JS中的完整可运行代码在这篇文章的底部,根据“拉伸”文本设计显示希伯来语文本的布局,如here所述。它看起来像这样在几个不同的字体大小:
32
它在这个“比例”下看起来最好(你可以编辑下面代码片段顶部的16
,它写着render(words, 16)
)。
16
它在这个规模上崩溃了,它在几个方面没有正确地布局。
下面是一个演示,展示了一个SVG图像上的边界框(理论上SVG是每行生成的,但请注意,它们不在一行上)。
问题
我如何让这个布局在不同的“字体大小”正确?(字体大小是您可以更改的16设置)。为什么我不能在SVG的每一行上说<svg width="100%" height={fontSize}>
,它只会占用整个父宽度?这样,如果屏幕是800px,字体大小是16,它将是长行的小文本,而在48px,这将是800px宽的“窄”行(因为字体大得多),和更多的行。我不明白为什么SVG不像我期望的那样工作。
主要的问题似乎归结为字母位置(在X轴上)没有被正确计算,那么如何解决这个问题呢?像R
和LETTER_OFFSET
这样的值我不确定这些“神奇值”是从哪里来的。
P.S.我从多年前的JavaScript代码中“移植”了这个文本布局代码到TypeScript,我不确定是我最初写的还是其他人写的。我似乎在网上找不到任何地方可以参考它,所以我不确定。
可运行示例
这里是完整的代码和演示数据。Here是更多的chapter
数据,如果你想在更多的文本上测试它(并不都符合StackOverflow的字符限制)。我认为LETTER_OFFSET
和SPACE_OFFSET
需要基于父宽度以某种方式动态计算,而不是硬编码。注意,如果更改这些硬编码值,字母有时会由于某种原因重叠,这使我相信字母布局计算(在x轴上)没有正确计算。
// this is the main drawing function,
// called at the very end.
function draw() {
const words = []
chapter.verses.forEach(v => {
words.push(
...String(v.value[0].text)
.split(/\s+/)
.map(word => {
return [...word].filter(c => ALPHABET.includes(c)).join("")
})
)
})
const lines16 = render(words, 16)
lines16.forEach(line => document.querySelector(".container-16")?.appendChild(line))
const lines32 = render(words, 32)
lines32.forEach(line => document.querySelector(".container-32")?.appendChild(line))
}
const chapter = {
verses: [
{
index: 0,
type: "verse",
value: [
{
language: "hebrew",
script: "hebrew",
text: "ויהיו חיי שרה מאה שנה ועשרים שנה ושבע שנים שני חיי שרה׃"
}
]
},
{
index: 1,
type: "verse",
value: [
{
language: "hebrew",
script: "hebrew",
text:
"ותמת שרה בקרית ארבע הוא חברון בארץ כנען ויבא אברהם לספד לשרה ולבכתה׃"
}
]
}
]
}
let measurer
function addToMeasurer(el) {
initializeMeasurer()
if (!measurer) {
return
}
measurer.appendChild(el)
}
function initializeMeasurer() {
if (measurer) {
return
}
measurer = document.createElement("span")
measurer.setAttribute("id", "measurer")
measurer.style.position = "absolute"
measurer.style.height = "auto"
measurer.style.width = "auto"
measurer.style.whiteSpace = "nowrap"
document.body.appendChild(measurer)
}
function removeFromMeasurer(el) {
if (!measurer) {
return
}
el.parentNode?.removeChild(el)
}
function setMeasurerFont({ family, size }) {
if (!measurer) {
return
}
if (family) {
measurer.style.fontFamily = family
}
if (size) {
measurer.style.fontSize = typeof size === "number" ? `${size}px` : size
}
}
const GLYPHS = {
// horiz-adv-x="646"
"\u05c6": `M62.5 425q7.5 70 40 139.5t72.5 151t110 223.5l-184 1q30 211 0 418l148 -1q3 40 -8 75.5t-21.5 62.5t-40.5 89h-23l6 60h74l3 -60h-23q27 -37 37.5 -63.5t22 -62.5t19.5 -101l35 7l-3 274h-23l4 60h76l4 -60h-23l4 -280h25q13 76 26.5 109t17 50t36.5 55h-24l4 70h73 l6 -70h-23q-25 -57 -34.5 -85.5t-20 -57.5t-17.5 -75l160 -1q-20 -241 -8 -425l-204 10q-70 -154 -83.5 -198t-33.5 -128t-13 -138t53 -54h321q1 -210 32 -420h-303q-223 30 -263.5 119t-40.5 162.5t7.5 143.5z`,
א: `M1110 0q-118 13 -157 59.5t-156 191.5l-521 541q-16 -93 -6 -277v-187q306 5 306 -98l-10 -84q-26 -73 -42 -104.5t-84 -181.5l-50 -14q59 124 18 144l-118 20q-71 1 -82 -67l-22 6l-2 67v45l1 78q0 35 10.5 146.5t0.5 199.5q-5 164 19 367l-155 187l33 426l-16 129
l36 -10l32 -152q52 -24 573 -628q101 87 159 168l-248 258l172 247l15 127l24 -4l10 -151l13 -19q199 -164 189 -278q-10 -140 -51 -161l-224 -243l152 -168l173 -150l50 83l19 -15l-35 -98z`,
ב: (p = 0) => `
M ${1242 + p} 421
L ${1218 + p} 0
H 69
L 83 211
L 77 422
L ${1032 + p} 424
L ${1015 + p} 906
L 67 908
L 69 1352
L 49 1464
L 86 1454
L 117 1354
L 179 1355
L 178 1624
H 153
V 1693
L 225 1694
V 1624
H 208
L 223 1358
H ${1113 + p}
L ${1143 + p} 1428
H ${1178 + p}
L ${1153 + p} 1358
L ${1148 + p} 936
L ${1142 + p} 420
L ${1202 + p} 421
L ${1237 + p} 511
L ${1270 + p} 501
Z
`,
ג: `M686 248q0 -248 -29 -248q-20 0 -22 27q-5 47 -63 232q-54 169 -102 389q-50 116 -91 229l-22 71l-262 -4q22 205 4 409l157 1q-8 73 -18 102.5t-17 53.5t-34 74h-25l5 50h74l4 -50h-27q36 -56 40 -79.5t15 -53.5t20 -93h39l-2 280h-24l6 60h68l8 -60h-24l6 -280h37
q16 76 27.5 101.5t22 49.5t40.5 69h-24l6 60h66l3 -60h-17q-29 -50 -43.5 -77.5t-24.5 -53.5t-19 -89h172q-30 -203 0 -407l-172 -3l60 -127q20 -59 57.5 -113.5t79.5 -145.5q45 -223 45 -314zM497 550q-13 -404 -68 -459.5t-150 -86.5l-104 -1l-170 -3q40 224 60 488
q65 -64 155 -58t179 57z`,
ד: (p = 0) => `
M ${1255 + p} 935
L ${1117 + p} 928
L ${1156 + p} 613
L ${1230 + p} 356
Q ${1265 + p} 142 ${1209 + p} 71
T ${1137 + p} 0
Q ${1090 + p} 0 ${1090 + p} 43
L ${1007 + p} 642
L ${988 + p} 930
L 109 926
Q 136 1102 116 1353
L 106 1444
H 134
L 159 1354
L 240 1359
L 242 1624
H 220
V 1684
L 300 1683
V 1622
L 274 1624
L 281 1358
H ${1225 + p}
L ${1260 + p} 1458
L ${1292 + p} 1448
L ${1274 + p} 1358
Q ${1230 + p} 1178 ${1255 + p} 935
Z
`,
ה: (p = 0) => `
M ${1260 + p} 1168
L ${1257 + p} 930
L ${1127 + p} 928
L ${1185 + p} 645
Q ${1242 + p} 393 ${1214 + p} 164
Q ${1215 + p} 142 ${1159 + p} 71
T ${1087 + p} 0
Q ${1050 + p} 0 ${1050 + p} 43
Q ${1050 + p} 219 ${987 + p} 926
L 128 930
L 126 1353
L 106 1454
H 144
L 172 1354
H 249
L 247 1602
L 226 1604
V 1680
L 313 1690
L 315 1604
H 290
L 291 1358
L ${1211 + p} 1356
L ${1250 + p} 1458
H ${1292 + p}
L ${1254 + p} 1358
Z
M 578 327
L 486 0
Q 317 20 237 73
T 138 331
Q 140 400 151 439
T 177 541.5
A 451.24 451.24 0 0 0 223 660
L 250 658
Q 242 587 246.5 548.5
T 282.5 457
Q 314 404 578 327
Z
`,
ו: `M567 164q0 -23 -36.5 -93.5t-62.5 -70.5q-44 0 -44 43l9 768q0 117 -87 117l-240 -6q2 255 -23 426l282 10q200 0 200 -297l11 -448q21 -205 -9 -449z`,
ז: `M637 941l-218 -3q41 -101 87 -280.5t36 -278.5q0 -113 -24 -249q-23 -130 -53 -130q-52 0 -52 23q0 -2 -3.5 49t-3.5 88q0 21 -30 177t-40 312q-10 73 -5 134t-11 161l-217 -1q7 184 -11 416l157 5q2 64 -3.5 90t-25.5 68.5t-40 71.5h-12l-2 60h67l4 -60h-16
q19 -26 34 -64.5t23.5 -70t16.5 -96.5l49 -2l10 288h-14l3 50h63l-4 -50h-14l3 -291l49 1q14 60 29.5 105.5t25 65t44.5 69.5h-15l5 60h55l2 -60l-17 1q-29 -55 -40 -72.5t-26 -65.5t-15 -102h149q-10 -283 0 -419z`,
ח: (p = 0) => `
M1279 928h-178q47 -387 48.5 -476t-11.5 -199.5t-54 -181.5t-57 -71q-57 0 -57 43q-30 176 -26.5 353.5t59.5 531.5h-177l-17 296l-118 232l-78 -198l-12 -326l-193 -5q42 -341 37 -479.5t-11.5 -293t-67.5 -154.5q-39 0 -39 41q-73 258 -71.5 415.5t44.5 471.5l-186 4
q12 190 -4 424q6 118 4 118h20l40 -116l438 6l99 252l111 -258h462q-14 -160 -5 -430z
`,
ט: `M1163.5 314.5q-109.5 -254.5 -293.5 -314.5h-235h-339l-64 956l-117 3q22 186 -6 393l125 2q-5 69 -9 98.5t-10 62.5t-35 79h-14l-1 60h64l3 -60h-17q29 -45 33 -73.5t9 -61.5t4 -105l40 -1l2 290l-14 -1l-4 60h65l3 -60h-17l2 -290h28q4 84 7.5 100t8.5 47.5t35 90.5
l-18 -1l-3 67l72 4l-1 -70l-14 -1q-26 -55 -31 -78t-11.5 -59.5t-0.5 -99.5h97q-5 -179 7 -399l-163 -4l49 -539l501 -20q147 0 185 90.5t53 205t-54 159.5q-19 95 -152 110t-241 -32q-44 -38 -57 -70t-49 -37l64 484q86 56 254.5 48t228 -100.5t91 -186.5t41.5 -166.5
t9 -199t-110.5 -381z`,
י: `M580 1031q0 -147 -80 -392q-51 -191 -112 -191q-28 0 -33 15v32q50 146 65.5 247.5t15.5 98.5q0 85 -136 87h-148l-25 -108l-16 2q-9 250 -9 371l-10 401h30l40 -236h149q267 0 269 -327z`,
ך: `M1250 1158q5 -227 5 -230l32 -1513l-97 13q-79 1366 -146 1433t-185 67h-684q21 194 11 310t-12 116l941 4q59 0 97 -47t38 -153z`,
כ: `M1198 678q0 -392 -168 -544q-148 -134 -500 -134h-501q22 210 4 420h669q171 0 285 54q78 118 78 214q0 240 -514 240t-514 4q22 210 12 316t-13 106l699 4q246 0 352 -150q111 -156 111 -530z`,
ל: (p = 0) => `
M ${1228 + p} 782
Q ${1228 + p} 560 ${1183.5 + p} 410.5
T ${1083.5 + p} 172
Q ${1028 + p} 83 ${953 + p} 41
T ${778 + p} -5
L ${530 + p} 7
Q ${549 + p} 187 ${536 + p} 393
L ${887 + p} 401
Q ${984 + p} 407 ${1027.5 + p} 441
T ${1086.5 + p} 540.5
Q ${1102 + p} 606 ${1106 + p} 701
Q ${1098 + p} 788 ${1083 + p} 818
T ${1025 + p} 887
Q ${982 + p} 926 ${889 + p} 926
L 264 936
Q 255 1020 255 1077.5
T 225 1396
L 213 1786
Q 213 1821 145 1818
L 101 1729
H 95
Q 113 1910 99 2039
L 90 2136
L 98 2142
L 128 2043
H 152
L 189 2163
H 199
L 202 2045
L 269 2032
Q 305 2023 311 2012
Q 325 1997 325 1941
A 827.14 827.14 0 0 0 320 1854
L 360 1359
L ${671 + p} 1358
Q ${833 + p} 1358 ${943 + p} 1294.5
T ${1116.5 + p} 1114
Q ${1180 + p} 997 ${1228 + p} 782
Z
`,
ם: `M378 924l8 -504h707l-7 397q-2 69 -39 92q-30 19 -106 19zM1231 0h-994l-11 928q-83 3 -101 7q14 190 4 304.5t-13 114.5l836 4q279 0 279 -297v-1061z`,
מ: `M1103 0h-585l-5 420h514q37 83 37 151q30 146 -63 252.5t-288 106.5q-208 30 -227 -110t-39 -277q0 -6 -21.5 -85t-41.5 -139q-50 -197 -179 -319q-39 0 -39 41l195 859q5 45 -1.5 63t-50.5 46l-196 57q29 182 44 418q145 -18 282 -45q71 -24 96 -61t40 -101.5t10 -119.5
q-71 -189 5 -146q65 102 80 141t21.5 108t6.5 162l153 -4q43 0 129 -44q89 -50 136 -123q113 -153 113 -447q-1 -75 -1 -184.5t-125 -619.5z`,
ן: `M567 905l-192 5q20 -151 35 -246l50 -479q-6 -269 -12.5 -444.5t-28 -245.5t-37.5 -70q-44 0 -44 43q-80 847 -75 1055t-11 389l-172 1q13 216 -1 426l139 3q2 49 -2 74t-9 48.5t-29 87.5h-26l3 70h79l-2 -69l-25 -1q33 -63 36.5 -84.5t7.5 -49.5t13 -79l31 -2l12 281
h-25l3 80l80 -1l-2 -78h-24l1 -279l32 1q11 60 15 78t13.5 49.5t43.5 83.5h-25v71h81l3 -70h-27q-29 -52 -38 -75t-17.5 -50.5t-8.5 -83.5l157 1q-12 -203 -2 -440z`,
נ: `M621.5 119q-40.5 -89 -263.5 -119h-303q31 210 32 420h321q46 0 53 54t-13 138t-33.5 128t-83.5 198l-204 -10q12 184 -8 425l160 1q-7 46 -17.5 75t-20 57.5t-34.5 85.5h-23l6 70h73l4 -70h-24q33 -38 36.5 -55t17 -50t26.5 -109h25l4 280h-23l4 60h76l4 -60h-23
l-3 -274l35 -7q8 65 19.5 101t22 62.5t37.5 63.5h-23l3 60h74l6 -60h-23q-30 -62 -40.5 -89t-21.5 -62.5t-8 -75.5l148 1q-30 -207 0 -418l-184 -1q70 -142 110 -223.5t72.5 -151t40 -139.5t7.5 -143.5t-40.5 -162.5z`,
ס: `M513 928l-125 -5q9 -149 9 -173q-10 -174 63 -260q97 -81 113 -81h119q172 0 213 35q100 52 125 108t35 126q0 156 -54 183q-83 77 -289 67h-209zM1198 678q0 -312 -127 -495q-145 -210 -444 -210q-105 0 -232 172q-114 152 -144 418q0 293 -8 365l-139 4q32 210 17 316
t-17 106l621 4q238 0 357 -178q116 -174 116 -502z`,
ע: `M1150 1083q0 -66 -221 -583q-35 -67 -84 -146.5t-96 -146.5t-122.5 -147t-558.5 -170l15 174l-15 174l320 63l-144 631l-143 2q24 196 4 429l144 -1q-20 48 -24 76t-8 59.5t-42 84.5h-15l2 50h68l-1 -50h-16q31 -24 34.5 -54.5t12.5 -54.5t13 -108l32 -2l5 270l-16 1
l4 50l62 -2l-2 -50l-19 -2l-1 -270l25 4q10 104 25 129.5t18 45.5t22 44h-16l3 53l68 -1l-2 -52h-16q-22 -43 -30.5 -69.5t-21.5 -70t-16 -76.5l116 -2q-13 -206 4 -428l-190 -6l145 -609q267 106 323.5 192t160.5 296q9 37 -26 62l-223 120q113 188 159 391l87 -56
q197 -73 197 -244z`,
ף: `M1193 -655q-55 0 -105 43l-30 1503q0 37 -38 37h-782l1 -342l405 -5q5 -41 5 -97q0 -85 -6 -155l-47 -4h-262q-37 4 -64.5 14t-39 28.5t-41.5 65.5t-62 330v584l-21 137l32 10l45 -146l93 10h655q288 0 289 -297z`,
פ: `M740 449q-447 -35 -515.5 30.5t-93.5 141t-42 212t-17 191.5l4 319l-3 141l22 30l35 -166l914 10l49 -156q72 -135 92 -522l-10 -477q-75 -196 -138 -259.5t-127 -78.5t-303 -55l-557 -10l18 440h899l75 1l13 201l-98 10q-10 319 -11 513l-771 3l1 -202l555 -5
q-5 -102 9 -312z`,
ץ: `M563 945l-172 -5q40 -181 97 -574q157 88 225 184t103 161.5t17.5 142t-183.5 183.5q84 149 150 354l5 139l21 -3l24 -141l150 -96q91 -68 106 -111.5t16.5 -74t-16.5 -82.5q-53 -142 -79.5 -186.5t-117.5 -190t-169 -213t-239 -152.5l37 -316q3 -204 -5 -331
t-28.5 -197.5t-76.5 -70.5q-17 0 -68 435l-72 1140l-179 -1q13 232 6 410l149 3q7 70 0 100t-13.5 59.5t-40.5 79.5l-16 1v60l70 -1l-1 -59h-16q35 -49 42.5 -78.5t10 -59t7.5 -98.5l32 1l8 290h-13l1 49h69l-2 -49h-17l-2 -290l30 -1q7 64 14.5 94.5t16.5 60.5t33 75l-16 1
l-1 60h76l-1 -60l-19 -1q-26 -48 -33.5 -77t-15.5 -57.5t-13 -96.5h111q-12 -173 -2 -410z`,
צ: `M1134 288q0 -61 -34 -135.5t-212 -154.5h-788q33 215 25 420h667l-497 518h-174q2 204 -9 426h141q-3 62 -14 93.5t-17.5 62.5t-28.5 93l-20 -3l2 51l75 6l-2 -50l-18 -3q24 -61 31 -92t14 -61.5t11 -92.5l29 -2l7 300h-15l1 42l67 2l-4 -44h-16l4 -300l29 2
q12 73 18.5 104t12.5 62.5t29 83.5h-17l2 46l63 1v-47h-17q-20 -52 -24.5 -83.5t-9.5 -62.5t-15 -104h103q-20 -205 2 -428l-105 -1l331 -233q138 143 139.5 166.5t-2.5 42.5t-128 147q150 189 214 308l31 153l4 27l14 -23l5 -152q141 -121 158.5 -166t13.5 -72.5t-9 -66
t-66.5 -135.5t-296.5 -287q178 -147 246 -209q50 -52 50 -149z`,
ק: `M1179 628q-5 -130 -13 -307.5t-150 -250t-224 -71.5h-99l-2 417h110l153 8q92 28 103 84.5t11 146.5q0 180 -38.5 211.5t-80 46.5t-144.5 15l-708 4q22 190 2 422h107l1 267l-32 1l1 69l96 -1l-1 -68h-33l5 -264h607q165 0 237 -101q77 -135 87 -317t5 -312zM477 262
h-140q34 -347 60.5 -462t19.5 -475h-55q-159 600 -113 934l-98 10l2 322h324v-329z`,
ר: (p = 0) => `
M ${1232 + p} 30
L ${1190 + p} 0
L ${1147 + p} 30
L ${1055 + p} 811
Q ${1055 + p} 928 ${876 + p} 928
H 244
Q 135 928 104 932
Q 126 1122 116 1238
T 104 1354
L ${960 + p} 1358
Q ${1238 + p} 1358 ${1239 + p} 1061
Z
`,
ש: `M1321 1095q20 -103 -67 -346q-36 -31 -108.5 -173t-153.5 -265t-223.5 -182t-469.5 -129l-132 938l-101 2q12 104 8 421l97 1q-14 66 -21 93.5t-14.5 55.5t-21.5 73h-17v50h72l2 -49l-17 -1q14 -45 21 -73t14 -55.5t21 -93.5l30 -3l5 280l-20 -2l2 51l58 1l1 -50h-19
l3 -280l30 2q14 68 21.5 96.5t14.5 57.5t21 76h-18l1 48l71 2l2 -48l-18 -1q-14 -48 -20.5 -76.5t-13 -57.5t-19.5 -96h97l3 -297v-122l-159 -6l85 -387q86 30 157 95.5t124.5 135.5t60 98t3.5 30.5t-15 17t-115 36.5l-49 10q28 162 1 379l4 142l12 10l34 -148
q147 -17 178 -34t67.5 -51.5t56.5 -115t5 -116.5t-33 -83t-61.5 -116.5t-127 -182.5t-271.5 -207l81 -50q438 -85 563.5 175.5t-1.5 308.5l-86 31l27 160l3 247l68 -6q129 -37 157.5 -58.5t72.5 -49.5q37 -57 37 -113z`,
ת: (p = 0) =>
`
M ${1255 + p} 194
Q ${1188 + p} 0 ${1173 + p} 0
Q ${1118 + p} 0 ${1118 + p} 43
Q ${1118 + p} 59 ${1075 + p} 400
T ${1032 + p} 811
L ${1022 + p} 928
H 310
Q 294 826 321 732
T 415 495
Q 482 352 472 286
Q 472 135 426 65
Q 383 0 317 0
H 23
L 82 420
H 204
Q 265 444 254 504
T 229 632
Q 215 700 208 765
T 206 939
L 106 943
Q 138 1133 123 1244
T 106 1355
L ${1195 + p} 1359
Q ${1201 + p} 1359 ${1201 + p} 1251
T ${1225 + p} 937
L ${1158 + p} 931
L ${1162 + p} 885
L ${1176 + p} 839
L ${1210 + p} 615
L ${1234 + p} 390
A 1715.2 1715.2 0 0 0 ${1258 + p} 196
Z
`
}
const ALPHABET = [
"\u05d0",
"\u05d1",
"\u05d2",
"\u05d3",
"\u05d4",
"\u05d5",
"\u05d6",
"\u05d7",
"\u05d8",
"\u05d9",
"\u05da",
"\u05db",
"\u05dc",
"\u05dd",
"\u05de",
"\u05df",
"\u05e0",
"\u05e1",
"\u05e2",
"\u05e3",
"\u05e4",
"\u05e5",
"\u05e6",
"\u05e7",
"\u05e8",
"\u05e9",
"\u05ea",
"\u05c6"
]
const scalePriority = {
ד: 1,
ה: 3,
//'ב': '3.5',
// ל: 2.5,
ר: 2,
ת: 4
}
const X = 2785 / 56
const Y = 3030
const R = X / Y
const LETTER_OFFSET = 1300
const SPACE_OFFSET = 0
function computeLines(words, state) {
let line = []
const lines = [line]
let i = 0
while (i < words.length) {
var word = words[i++]
var list = line.concat(word)
//if (list.join('').length > 28) {
// line = [word.text]
// lines.push(line)
//} else {
var text = list.join("")
var size = sumLine(
text.split("").map(x => {
return { adjust: 0, symbol: x }
}),
state
)
if (size > state.maxWidth) {
line = [word]
lines.push(line)
} else {
line.push(word)
}
//}
}
return lines
}
function getOffset(glyph, state) {
const idx = state.alphabet.indexOf(glyph.symbol)
const bounds = state.bounds[idx]
const width = bounds.width + glyph.adjust
return R * width + LETTER_OFFSET
}
function initializeState(fontSize, maxWidth) {
const alphabet = ALPHABET
const bounds = measureAllSVGGlyphs(alphabet, fontSize)
return { alphabet, bounds, fontSize, lineOffset: 0, maxWidth }
}
function formatLine(line, state) {
const glyphs = line
.join(" ")
.split("")
.map(x => {
return { adjust: 0, symbol: x }
})
if (glyphs[glyphs.length - 1].symbol == " ") {
glyphs.pop()
}
if (glyphs[0].symbol == " ") {
glyphs.shift()
}
let initialSize = Math.ceil(sumLine(glyphs, state))
const idealWidth = state.maxWidth
if (initialSize === idealWidth) {
return glyphs
}
let remaining = idealWidth - initialSize
const stretchA = []
const stretchB = []
const stretchC = []
const stretchD = []
glyphs.forEach((br, i) => {
if (!glyphs[i + 1]) {
if (scalePriority[br.symbol]) {
// stretchA.push({
// glyph: br,
// scalePriority: scalePriority[br.symbol],
// })
}
} else if (glyphs[i + 1].symbol == " ") {
// last symbol
if (scalePriority[br.symbol]) {
stretchA.push({
glyph: br,
scalePriority: scalePriority[br.symbol]
})
}
} else if (!glyphs[i + 2]) {
if (scalePriority[br.symbol]) {
stretchB.push({
glyph: br,
scalePriority: scalePriority[br.symbol]
})
}
} else if (glyphs[i + 2].symbol == " ") {
// last symbol
if (scalePriority[br.symbol]) {
stretchB.push({
glyph: br,
scalePriority: scalePriority[br.symbol]
})
}
} else if (!glyphs[i + 3]) {
if (scalePriority[br.symbol]) {
stretchC.push({
glyph: br,
scalePriority: scalePriority[br.symbol]
})
}
} else if (
glyphs
.slice(i, i + 3)
.map(x => x.symbol)
.join()
.match(/^\w+$/)
) {
// last symbol
if (scalePriority[br.symbol]) {
stretchC.push({
glyph: br,
scalePriority: scalePriority[br.symbol]
})
}
} else if (
glyphs
.slice(i, i + 4)
.map(x => x.symbol)
.join()
.match(/^\w+$/)
) {
// last symbol
if (scalePriority[br.symbol]) {
stretchD.push({
glyph: br,
scalePriority: scalePriority[br.symbol]
})
}
} else if (glyphs[i - 1] && glyphs[i - 1].symbol == " ") {
// last symbol
if (scalePriority[br.symbol]) {
stretchD.push({
glyph: br,
scalePriority: scalePriority[br.symbol]
})
}
}
})
stretchA.sort((a, b) => a.scalePriority - b.scalePriority)
stretchB.sort((a, b) => a.scalePriority - b.scalePriority)
stretchC.sort((a, b) => a.scalePriority - b.scalePriority)
stretchD.sort((a, b) => a.scalePriority - b.scalePriority)
// var spectrum = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
var spectrum = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
var spectrum2 = []
if (stretchA.length) {
spectrum2.push(1.0)
} else {
spectrum2.push(0.0)
}
if (stretchB.length) {
spectrum2.push(1.0)
} else {
spectrum2.push(0.0)
}
if (stretchC.length) {
spectrum2.push(1.0)
} else {
spectrum2.push(0.0)
}
if (stretchD.length) {
spectrum2.push(1.0)
} else {
spectrum2.push(0.0)
}
var totals = splitSpectrum(remaining, spectrum2)
var adjustmentsA = stretchA.length
? splitSpectrum(totals[0], spectrum.slice(0, stretchA.length))
: []
var adjustmentsB = stretchB.length
? splitSpectrum(totals[1], spectrum.slice(0, stretchB.length))
: []
var adjustmentsC = stretchC.length
? splitSpectrum(totals[2], spectrum.slice(0, stretchC.length))
: []
var adjustmentsD = stretchD.length
? splitSpectrum(totals[3], spectrum.slice(0, stretchD.length))
: []
while (adjustmentsA.length) {
var i = getRandomInteger(0, adjustmentsA.length - 1)
var y = stretchA[i]
var x = adjustmentsA[i]
adjustmentsA.splice(i, 1)
stretchA.splice(i, 1)
remaining -= x
y.glyph.adjust = x
}
//if (remaining) {
while (remaining && adjustmentsB.length) {
var i = getRandomInteger(0, adjustmentsB.length - 1)
var y = stretchB[i]
var x = adjustmentsB[i]
adjustmentsB.splice(i, 1)
stretchB.splice(i, 1)
remaining -= x
y.glyph.adjust = x
}
//}
//if (remaining) {
while (remaining && adjustmentsC.length) {
var i = getRandomInteger(0, adjustmentsC.length - 1)
var y = stretchC[i]
var x = adjustmentsC[i]
adjustmentsC.splice(i, 1)
stretchC.splice(i, 1)
remaining -= x
y.glyph.adjust = x
}
//}
//if (remaining) {
while (remaining && adjustmentsD.length) {
var i = getRandomInteger(0, adjustmentsD.length - 1)
var y = stretchD[i]
var x = adjustmentsD[i]
adjustmentsD.splice(i, 1)
stretchD.splice(i, 1)
remaining -= x
y.glyph.adjust = x
}
//}
return glyphs
}
function createDynamicPath(index, path, symbol, scale, state) {
const i = state.alphabet.indexOf(symbol)
const bounds = state.bounds[i]
const width = bounds.width
state.lineOffset += R * width + scale + LETTER_OFFSET
const transform = `translate(${state.maxWidth -
state.lineOffset}, 0) scale(${1},-${1})`
let el = document.createElementNS("http://www.w3.org/2000/svg", "path")
el.setAttribute("d", path.replace(/\s+/g, " "))
el.setAttribute("transform", transform)
return el
}
function layout(lines, state) {
const svgs = []
let I = 0
lines.forEach((line, i) => {
state.lineOffset = 0
const glyphs =
i != lines.length - 1
? formatLine(line, state)
: line
.join(" ")
.split("")
.map(x => {
return { adjust: 0, symbol: x }
})
const paths = []
glyphs.forEach(glyph => {
const g = GLYPHS[glyph.symbol]
const text = typeof g == "function" ? g(glyph.adjust) : g
if (glyph.symbol == " " || glyph.symbol == "\u00a0") {
state.lineOffset += SPACE_OFFSET
} else {
paths.push(
createDynamicPath(I, text, glyph.symbol, glyph.adjust, state)
)
I++
}
})
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
svg.setAttribute("version", "1.1")
svg.setAttribute("viewBox", `0 -1900 ${state.maxWidth} 3030`)
svg.setAttribute("height", `${state.fontSize}`)
paths.forEach(path => svg.appendChild(path))
svgs.push(svg)
state.lineOffset = 0
})
return svgs
}
function measureAllSVGGlyphs(glyphs, fontSize) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
svg.setAttribute("viewBox", "0 -900 52000 3030")
svg.setAttribute("version", "1.1")
svg.setAttribute("height", `${fontSize}`)
addToMeasurer(svg)
const measurements = glyphs.map(symbol =>
measureSVGGlyph(svg, GLYPHS[symbol])
)
removeFromMeasurer(svg)
return measurements
}
function measureSVGGlyph(svg, path) {
// document.body.style['line-height'] = LINE_HEIGHT + 'px'
// document.body.style['font-size'] = FONT_SIZE + 'px'
const el = document.createElementNS("http://www.w3.org/2000/svg", "path")
el.setAttribute(
"d",
(typeof path == "function" ? path() : path).replace(/\s+/g, " ")
)
svg.appendChild(el)
const rect = el.getBoundingClientRect()
svg.removeChild(el)
return rect
// return svg.querySelector('path').getBoundingClientRect()
}
function render(words, fontSize, maxWidth = 49000) {
const state = initializeState(fontSize, maxWidth)
const lines = computeLines(words, state)
console.log(lines)
const svgs = layout(lines, state)
return svgs
}
function getRandomInteger(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min)) + min
}
function sumLine(glyphs, state) {
let sum = 0
glyphs.forEach(glyph => {
if (glyph.symbol == " " || glyph.symbol == "\u00a0") {
sum += SPACE_OFFSET
} else {
sum += getOffset(glyph, state)
}
})
return sum
}
function splitSpectrum(total, spectrum) {
var smallSum = 0
spectrum.forEach(x => (smallSum += x))
var ratios = spectrum.map(x => x / smallSum)
return ratios.map(x => x * total)
}
draw()
.container {
width: 800px;
}
<h2>16 Font Size</h2>
<div class="container container-16"></div>
<h2>32 Font Size</h2>
<div class="container container-32"></div>
关于x轴上的布局不正确,请参阅32字体大小示例中第二行的第一个字符(从右边开始),如下所示:
这是“第一节”的结尾,有这两个字:חיי שרה
,所以单词没有作为一个单元保存。如果没有拉伸字母,它应该看起来更像这样:
1条答案
按热度按时间e4eetjau1#
也许一个务实的方法是可以接受的:
为什么不简单地使用CSS
scale
并根据设备大小使用CSS * 自定义属性 * 将render(words, 32)
版本扩展到所需的大小?您仍然需要注意适当的 Package ,但HTML引擎在呈现缩放内容方面做得很好。
我调整了你的JS,只创建了一组大小为
render(words, 32)
的行,并创建了一个简单的范围滑块,用于调整CSS * 自定义属性 *--scale
,以相对于32px
的所需比例显示文本。