免责声明

免责声明

市场上有售的海报,可以一次性提供一本书的全部内容,并带有一些巧妙的文字流动,例如这个霍比特人海报

假设我有这本书的文本卡夫卡的《变形记》并画出如下轮廓:

在此处输入图片描述

我怎样才能在 LaTeX 中做到这一点?

答案1

这个问题是一个很大的挑战!

完整2.pdf

由于 TeX 是一个强大且可扩展的系统(尤其是 LuaTeX),所以这是可能的(如上所述)。

免责声明

但在描述解决方案之前,我首先要劝阻大家不要使用 TeX 来完成这项工作:

  • 这种事情并不是 TeX 和 LaTeX 的设计初衷(分别是制作精美的书籍和结构化文档)。因此,当您将 (La)TeX 用于此目的时,您会在某种程度上与系统作斗争。(例如,TeX 在段落之间插入的可拉伸垂直粘连有利于生成齐平页面,并具有合适的分页符,避免出现孤行和孤行,但它很难知道某个段落将落在页面上的哪个位置。同样,TeX 丢弃页面开头的任何粘连的功能对于书籍来说很有意义,但在这种情况下,我们必须找到抑制该功能的变通方法。)因此,最好使用其他系统,即为不同类型的页面布局应用程序而设计的系统。

  • 举一个仍然基于 TeX 的“另一个系统”的有趣例子,请考虑Speedata 出版商,作者:Patrick Gundlach,又名顶部跳过。 (看例子GitHub。)当我在下面编写 LuaTeX 代码并在线搜索相关代码时,这个问题经常出现。与下面我的探索性尝试相比,它似乎是一个更“生产质量”的系统。(我没有用过它,不知道它是否有一种简单的方法来完成问题中的特定任务;只是说存在其他系统,并不是所有事情都必须用 TeX 来完成。)

  • 为了真正令人满意的结果工作,你需要好的设计。这就是无骨经典(问题海报的制作者)做了什么,以及为什么,如果你有兴趣拥有这样的海报,他们的海报值得购买。(例如,他们的爱丽丝海报排版老鼠的故事)即使您想自己制作海报,您也可能希望使用具有图形界面的页面布局程序,这样可以更轻松地进行设计工作(例如进行增量更改并获得即时的视觉反馈)。请参阅RobtAll 的回答您可以期待此类软件带来的体验。

然而,如何在现有的 TeX 系统内部做到这一点是一个有趣的问题,这就是我在下面所回答的。


用法

使用下面的代码并安装 ImageMagick,我们就可以生成上面的排版。

  1. 首先,为了确定我们大约需要的页数,获取我们的输入文件(这里我们可以pg5200.txt删除一些无关的内容,特别是包含“EBook #5200”的行,由于类别代码的缘故,这一行会让 TeX 感到困惑#)并对其进行排版。

    \input pg5200.txt
    \bye
    

    在这种情况下,它大约有 25 页。这建议布局为 5x5 页,考虑到空白区域(图像剪切)的松弛,我们将使用 5x6。(我们也可以使用 6x5,这将更好地填充 A0 页面。)

  2. 下载剪切图像。在本例中,我将使用暂无相关信息从问题中。

  3. 接下来,使用 LuaTeX 排版输入文件。由于我们不需要 LaTeX 的任何特殊功能(除了)ltluatex,因此我们可以使用普通的 TeX 来简化操作:使用以下代码编译以下内容luatex -shell-escape kafka.tex

    \input{ltluatex} % For luatexbase.add_to_callback
    \directlua{dofile('pages-cutout.lua')}
    \directlua{pagesWithCutout('pg5200.txt', 5, 6, 'x8se9.jpg')}
    \bye
    

    如果你愿意的话,我们可以使用 LaTeX,尽管我还没有用花哨的 LaTeX 文档测试过它:使用以下命令编译以下内容lualatex -shell-escape kafka.tex

    \documentclass{article}
    \usepackage[margin=1in]{geometry}
    \begin{document}
    \directlua{dofile('pages-cutout.lua')}
    \directlua{pagesWithCutout('pg5200.txt', 5, 6, 'x8se9.jpg')}
    \end{document}
    
  4. 最后,将页面排列kafka.pdf在一起,使用pdfpages

    \documentclass{article}
    \usepackage[a0paper,margin=0cm]{geometry}
    \usepackage{pdfpages}
    \begin{document}
    \includepdf[pages=-,nup=6x5,column,delta=-3cm -4cm]{kafka.pdf}
    \end{document}
    

    你可以用任何引擎(lualatexxelatexpdflatex)进行编译。结果如上图所示。

显然,所有神奇的事情都发生在步骤 3 中pages-cutout.lua,因此该答案的其余部分给出了代码并对其进行了解释。


TeX 中段落形状的历史

(如果您对历史不感兴趣,请跳过此部分。)

TeX 本身具有一个\parshape可以与段落一起使用的基本功能,用于指定该段落中每行的宽度和缩进。

拖船8:1,1987 年 4 月,Donald Knuth 和 Alan Hoenig 同时发表周六早上的问题  和TeX 在 Windows 上的表现:进度报告分别。问题是相同的:排版包含矩形“洞”或“窗口”的段落。在下一期(拖船8:2,1987 年 7 月),并公布了解决方案。

Knuth 的单页解决方案是使用特定的 parshape(左对齐和右对齐的短行)排版段落,然后将页面高度(\vsize)设置得非常小,以便在每行之后调用输出例程,并重新定义输出例程(\output),以便它不会发送页面,而是首先将这些行高“页面”收集到框中,最后将它们排版到\vskip-\baselineskip适当的位置以进行重叠。(使用 LuaTeX,我们不需要这种调用技巧\output;我们只需使用像 这样的回调即可post_linebreak_filter。)

Hoenig 的解决方案附有“之前”和“之后”的有用说明:

Hoenig 的解决方案

他的解决方案是使用\parshape,生成上图中的顶部段落,然后使用\vsplit拉出段落顶部(“门楣”)、中间(“侧面”)和底部(“窗台”)部分,围绕“窗户”,然后\vsplit to \baselineskip在“侧面”重复,将各个线条放入组装在一起的框中。这个解决方案成为了cutwin包裹。

然后是shapepar包裹。它的文档没有描述其中的想法,我也没有读过shapepar.sty足够详细地了解除了它也使用之外的所有内容\parshape。这个包真的很酷很复杂,并且适用于 LaTeX 和纯 TeX。我认真考虑过用它来排版这个问题中的各个段落。但我无法弄清楚如何将任意图像(或扫描线图案)转换为其形状规范(我相信这是可能的),所以我放弃了并编写了自己的实现。


代码概要

(我个人认为想法比代码更重要,因此我认为 TeX/LaTeX 社区的做法存在问题,他们通常只为最终用户提供“打包”的解决方案,而没有付出更多努力来实现分享知识/技巧这将有助于其他人学习和借鉴解决方案。因此,我将尝试在下面解释所有内容。

核心思想如下:

  • 对于每个段落,我们需要用适当的“形状”来排版该段落。(给定形状,这将成为上面历史部分提到的问题。)我们如何确定这个形状?

  • 通过了解整体图像、原始图像被分割成的行数和列数(页面数)、此段落位于哪一页以及此段落位于页面的哪个位置,我们可以确定到达段落开头时该段落的形状。(也就是说,每个段落的形状都是作为全局图像的偏移量确定的。)段落在页面上的位置由页面上已放的内容量决定,该内容量在 TeX 维度中可用pagetotal

  • 对于我们的目的而言,确定图像意味着什么?在排版段落时,我们最终想要的是知道每行文本的位置以及“孔”的位置。这意味着将原始图像量化为长度为二进制(黑白/孔和文本)的运行,其高度为行的高度(\baselineskip)。为了获得良好的图像保真度,我们可以在每个段落的开头执行此操作,但为了提高速度,我们可以在开头执行一次(假设行高恒定),然后将段落作为偏移量索引到全局图像数据中。


例子

得到具有以下形状的段落:

异形件

我们首先确定适当的形状,使用适当的 parshape 进行断线,如下所示,然后插入负胶水以使线条重叠:

负片胶前异形件


代码演练/详细信息

(下面的代码片段pages-cutout.lua删除了一些内容;整个代码作为单个文件稍后链接在下面。)

段落的位置

在段落的开头(例如在pre_linebreak_filter或 中linebreak_filter),我们可以按如下方式获取页面的位置。按照惯例,页码在 中可用\count0,在 LuaTeX 中可直接访问tex.count[0]。已添加到页面的材料的高度(在此段落之前)在 TeX 维度\pagetotal(或tex.pagetotal)中可用。(如果我们在页面上有可拉伸/收缩的垂直页面,则该系列中的其他维度 — pagefilstretchpagefillstretchpagefilllstretchpageshrinkpagedepthpagegoal可能也很有趣,我们将在此避免。)

对于为剪切图选择的原始图像,我们想知道从图像中段落开始的位置到当前页面末尾的图像区域。如果我们的页面出现在总页面的行r(共R)和列c(共)上,那么,给定页面垂直高度 (vsize) 的Cpagetotal ,我们感兴趣的图像部分是具有以下角的矩形(坐标为 ,从左上角开始,沿页面向下增加):tv(x,y)(0,0)y

((c-1)/C, (r - 1 + t/v)/R)               (c/C, (r - 1 + t/v)/R)
                             . . .
((c-1)/C, r/R)                           (c/C, r/R)

这些是相对于图像总面积的坐标。从绝对意义上讲,如果我们将图像缩放到 1 像素为h一行文本的高度(文本本身可能会变化,但我们以本段开头的行高为准),那么缩放系数就是(v / h) * R(行数)除以图像的原始高度(以像素为单位)。

(由于一个段落可以跨越多页,因此我们还需要包括下一页。如果有跨越更多页面的段落,也应该包括这些段落。)

local utils = require('utils.lua')
global = {}

-- Rest of current page + next page
function image_areas_for_paragraph(resized_image_height)
   local area1 = image_offsets(tex.count[0], tex.pagetotal / tex.vsize, resized_image_height)
   local area2 = image_offsets(tex.count[0] + 1, 0, resized_image_height)
   return area1, area2
end

-- Which row and column a given page number falls on.
local function row_column(page_number)
   local current_column = math.ceil(page_number / global.num_rows)
   local current_row = page_number - (current_column - 1) * global.num_rows
   return current_row, current_column
end

-- The crop area, for this page number and page-filled fraction
function image_offsets(page_number, f, resized_image_height)
   local r, c = row_column(page_number)
   if r > global.num_rows or c > global.num_columns then
      return nil
   end
   local C = global.num_columns
   local R = global.num_rows
   local x1 = (c - 1)/C
   local y1 = (r - 1 + f)/R
   local x2 = c/C
   local y2 = r/R
   local image_resize_ratio = resized_image_height / global.image_height
   local resized_image_width = image_resize_ratio * global.image_width
   local x_start = math.floor(x1 * resized_image_width + 0.5) -- The left edge of the “page” (text area) starts here
   local y_start = math.floor(y1 * resized_image_height + 0.5)
   local x_end = math.floor(x2 * resized_image_width + 0.5) -- The right edge of the “page” (text area) ends here
   local y_end = math.floor(y2 * resized_image_height + 0.5)
   if y_start >= resized_image_height then
      return nil
   end
   local offset_string = string.format("%dx%d+%d+%d", x_end - x_start + 1, y_end - y_start + 1, x_start, y_start)
   return offset_string
end

以可用形式获取图像

现在我们知道了我们感兴趣的图像区域,甚至知道如何缩放它,以便一行像素对应一行文本,剩下的就是将图像的这个区域转换为二进制数据(说明文本可以放在哪里以及空格必须放在哪里),以便在 LuaTeX 内部使用。我们的秘密武器是可移植位图格式 (PBM). 原始图像,转换为 PBM 的方式如下:

convert x8se9.jpg -compress none bw.pbm

(这里convert是 ImageMagick)生成一个纯 ASCII 文件,可以在图像查看器中查看:

默认bw.pbm

并在文本编辑器中查看(单击图像以全尺寸查看):

Emacs 的屏幕截图

正如您所看到的,PBM 文件的格式非常简单 - 只有 0 和 1 - 使得从 Lua 代码中“理解”图像变得相当容易。

这里其实还有另一个微妙之处,我花了一些时间才弄清楚。如果你对每个段落convert单独运行上面的程序,那么它会根据最佳情况将图像的每个区域转换为黑白仅适用于该地区。这种不一致会导致整个图像效果不佳(某些“白色”区域可能只是因为它们局部比周围的白色像素更暗而变黑)。所以最好使用固定阈值;我曾经convert x8se9.jpg -resize x265 -threshold "85%" bw.pbm得到以下结果:

阈值带宽

(当然,如果您已经拥有良好的黑白原始图像,则不需要任何这些。)

--[======================================================================[
   Using an area of image, translate into "runs" of 0s and 1s
--]======================================================================]
function get_runs()
   local base_filename = 'tmp-for-paragraph.pbm'
   -- We want to scale the image so that a height of \baselineskip is 1 pixel
   -- => total number of rows of pixels should be: (vsize/baselineskip)*(num_rows)
   local resized_image_height = math.floor(tex.vsize / tex.baselineskip.width) * global.num_rows
   local area1, area2 = image_areas_for_paragraph(resized_image_height)
   local filenames = {}
   local command =
      function(area, filename)
         return string.format(
                    [[convert "%s" -resize "x%s" -crop "%s" -threshold "85%%" -compress none "%s"]],
                    global.image_filename, resized_image_height, area, utils.safe_filename(filename))
      end
   if area1 ~= nil then
      local filename1 = '1' .. base_filename
      print(command(area1, filename1))
      os.execute(command(area1, filename1))
      table.insert(filenames, filename1)
   end
   if area2 ~= nil then
      local filename2 = '2' .. base_filename
      print(command(area2, filename2))
      os.execute(command(area2, filename2))
      table.insert(filenames, filename2)
   end
   local ret = {}
   for unused_filename_number, filename in ipairs(filenames) do
      local line_number = 0
      for line in io.lines(filename) do
         line_number = line_number + 1
         if line_number > 2 then  -- Exclude first two header lines
            local runs = {}
            local char = '0'  -- 0 is white in image, which means text in paragraph
            local run_length = 0
            for c in string.gmatch(line, '%d') do
               if c == char then
                  run_length = run_length + 1
               else
                  if char == '1' then run_length = -run_length end -- Black pixels are glue, negative.
                  table.insert(runs, run_length)
                  char = c
                  run_length = 1
               end
            end
            -- The leftover last run
            if char == '1' then run_length = -run_length end
            table.insert(runs, run_length)
            table.insert(ret, runs)
         end
      end
   end
   collectgarbage()  -- https://tex.stackexchange.com/a/404623/48
   return ret
end

将图像数据转换为 parshape

现在,我们已经将图像中的 0 和 1 转换为文本和非文本长度的“运行”,我们需要将其作为 parshape 输入 TeX。(这里的改进是丢弃任何占据行的极小部分的“文本”运行,因为我们不想要只有一个字母宽度的文本。)对于每个文本运行,我们在 parshape 中创建一个该长度的“行”,缩进等于之前所有内容的总和。(类似于 Knuth 对窗口问题的解决方案:参见上面“示例”部分中的图像。)稍后我们将把这些框放在一起。注意:这里我们还需要维护有关特定行是否应排版在前一行之上(重叠)的数据,以及此行是否应以“空白行”开头(如果是,则有多少)。作为一个小小的技巧,我将这些数据保存在数组的第三个元素中,尽管最好将它们分开保存。

--[======================================================================[
   Translate runs into a parshape, avoiding tiny runs of text
--]======================================================================]
function get_paragraph_spec()
   local runs = get_runs()
   runs = clean_runs(runs, 0.02)
   local ret = runs_to_parshape(runs)
   return ret
end

-- Removes all text (positive numbers) that have width less than min_frac of the total length of the line.
-- This function can probably be simplified, as it looks like a lot of code for something so simple.
function clean_runs(runs, min_frac)
   local ret = {}
   for i, linespec in ipairs(runs) do
      local linesum = 0
      for j, elemspec in ipairs(linespec) do linesum = linesum + math.abs(elemspec) end
      local newlinespec = {}
      local add_to_next_glue = 0
      for j, elemspec in ipairs(linespec) do
         if elemspec > 0 then
            if elemspec / linesum < min_frac then
               if #newlinespec > 0 then
                  newlinespec[#newlinespec] = newlinespec[#newlinespec] - elemspec
               else
                  add_to_next_glue = add_to_next_glue + elemspec
               end
            else
               table.insert(newlinespec, elemspec)
            end
         else
            elemspec = elemspec - add_to_next_glue
            add_to_next_glue = 0
            if #newlinespec > 0 and newlinespec[#newlinespec] <= 0 then
               newlinespec[#newlinespec] = newlinespec[#newlinespec] + elemspec
            else
               table.insert(newlinespec, elemspec)
            end
         end
      end
      table.insert(ret, newlinespec)
   end
   return ret
end

function runs_to_parshape(runs)
   --[[
      Example: for a paragraph shaped like
          aaaaaaaaaaaaaa       aaaaaaaaaaaaaa
          bbb     bbbbbb   bbbbbbb   bbbbbbbb
          ccccccccccccccccccccc              
                          ddddd              
       and therefore input (`runs`) like:
           {
              {14, -7, 14},
              {3, -5, 6, -3, 7, -3, 8},
              {21, -14},
              {-16, 5, -14},
           }
      this function returns
   {
      {0            , hsize * 14/35},
      {hsize * 21/35, hsize * 14/35},
      {0            , hsize *  3/35},
      {hsize *  8/35, hsize *  6/35},
      {hsize * 17/35, hsize *  7/35},
      {hsize * 27/35, hsize *  8/35},
      {0            , hsize * 21/35},
      {hsize * 16/35, hsize *  5/35},
      {0, hsize},
   }
   --]]
   local hsize = tex.hsize
   local myparshape = {{hsize, 0, 0}}
   local prev_baselineskip_glue = 0 -- How many multiples of baselineskip to add before a line
   for i, linespec in ipairs(runs) do
      local linesum = 0
      for j, elemspec in ipairs(linespec) do linesum = linesum + math.abs(elemspec) end
      local cursum = 0
      if prev_baselineskip_glue < 0 then
         prev_baselineskip_glue = 0
      end
      for j, elemspec in ipairs(linespec) do
         if elemspec > 0 then
            table.insert(myparshape, {hsize * cursum / linesum, hsize * elemspec / linesum, prev_baselineskip_glue})
            prev_baselineskip_glue = -1 -- Because after the first line, we need to add a negative glue each time
         end
         cursum = cursum + math.abs(elemspec)
      end
      if prev_baselineskip_glue ~= -1 then
         -- No text has been added so this line is fully glue, which means the next line must be preceded by \baselineskip
         assert(cursum == linesum)
         myparshape[#myparshape][3] = myparshape[#myparshape][3] + 1
      end
   end
   table.insert(myparshape, {0, hsize, 0})
   return myparshape
end

综合起来

代码的其余部分。:-) 在这里我们调整一些 TeX 参数并设置一个linebreak_filterLuaTeX 调用的函数,以便将任何段落拆分成行。此过滤器使用上面前几节中的所有代码来获取 parshape,然后将 TeX 的默认换行 ( tex.linebreak) 与该 parshape 结合使用。然后,它插入适当的胶水(负或正),以便每个“行”都与前一个“行”有正确的偏移量。请注意,当我们想要“空行”(在段落顶部或内部)时,我们应该小心在它之前插入一些不可丢弃的内容(这里是规则),以便 TeX 不会丢弃此胶水(例如在分页符处)。这花了一些时间才弄清楚。

--[======================================================================[
   Putting it all together
--]======================================================================]
-- The main function / “interface” to this code.
function pagesWithCutout(text_filename, num_rows, num_columns, image_filename, is_latex)
   image_filename = utils.safe_filename(image_filename)
   global.image_filename = image_filename
   global.image_width  = tonumber(utils.get_output('identify -format "%w" ' .. image_filename))
   global.image_height = tonumber(utils.get_output('identify -format "%h" ' .. image_filename))
   global.num_rows = num_rows
   global.num_columns = num_columns
   local setup = nil
   if is_latex then setup = [[\pagestyle{empty}]] else setup = [[\nopagenumbers]] end -- Turn off page numbers
   setup = setup .. [[\parskip=5pt \raggedbottom]] -- So that inter-paragraph glue stretch does not cause problems
   setup = setup .. [[\hyphenpenalty=0 \lefthyphenmin=1 \righthyphenmin=1 \tolerance=9999 \emergencystretch=3em ]] -- Avoiding overfull boxes as much as possible
   setup = setup .. [[\overfullrule=0pt\relax ]] -- For the few overfull boxes that do happen
   tex.print(setup)
   luatexbase.add_to_callback('linebreak_filter', shape_paragraph, 'Typeset each paragraph according to the "shape" from image.')
end

-- A linebreak_filter: For a given paragraph, determines the required shape and typesets accordingly.
function shape_paragraph(head, is_display)
   local myparshape = get_paragraph_spec()
   local leading_glue = table.remove(myparshape, 1)
   local broken, info = tex.linebreak(head, {parshape=myparshape})
   tex.prevdepth = info.prevdepth -- https://tex.stackexchange.com/q/403801/48
   tex.prevgraf = info.prevgraf
   -- Insert proper glue (negative `baselineskip`s) so that the lines overlap as they should.
   local tmp = broken
   -- First insert the leading glue
   local tmp = utils.find_first_of_type_in(broken, 'hlist')
   assert(tmp ~= nil, 'Empty paragraph? Nowhere to insert this glue')
   broken = insert_nondiscardable_glue_before(broken, tmp, tex.baselineskip.width, leading_glue[3])
   -- Next insert the rest of the glue
   for i, linespec in ipairs(myparshape) do
      tmp = utils.find_first_of_type_in(tmp, 'hlist')
      if tmp == nil then break end
      -- Insert `linespec[3]` number of baselineskip glue before `tmp`
      broken = insert_nondiscardable_glue_before(broken, tmp, tex.baselineskip.width, linespec[3])
      tmp = tmp.next
   end
   return broken
end

function insert_nondiscardable_glue_before(head, tmp, glue_width, times)
   for i = 1, math.abs(times) do
      local my_glue = node.new('glue')
      node.setglue(my_glue, glue_width * utils.sign(times))
      head = node.insert_before(head, tmp, my_glue)
      local rule = node.new('rule')
      rule.height = 0
      rule.depth = 0
      rule.width = 0
      rule.subtype = 1  -- box (see LuaTeX manual)
      head = node.insert_before(head, my_glue, rule)
   end
   return head
end

以上内容大致如下pages-cutout.lua,它使用了一些实用函数,我将其移至单独的utils.lua。它不符合此答案的 30000 个字符限制,因此我将两个文件放在一起这里


可能相关

在做这一切的时候,我遇到了一些其他可能有用/相关的 LuaTeX 资料来源,我还没有研究过:卢阿特克斯michal-h21/换行。我注意到 Speedata Publisher 使用图像塑造者(调用 ImageMagick)来解决类似的问题。还有一些其他的 LuaTeX 好东西:拖船道格拉斯遍历可视化节点树post_linebreak_filter节点返回文本拖船,…


最后的话

这是使用 LaTeX 而非纯 TeX 的结果,并且页面布局为 6x5 而非 5x6:

完整3.pdf

注意:所有这些仅针对此文本+图像进行测试;其余的错​​误/增强功能(昆虫腹部的那些零散的文字,保留图像的纵横比,考虑边距,可以想到更多)留给雄心勃勃的读者。:-)

答案2

我认为这应该得到负面回应。

ShreevatsaR 提出的解决方案(编辑:最初)很有趣。(编辑:更新后的回复非常吸引人。)但我认为最初的问题不是针对 LaTeX 的。它是针对页面布局程序的。霍比特人海报很可能是使用 InDesign 或 Quark Xpress 制作的。唯一的开源项目(据我所知)是 Scribus,我相信它可以做到。无论如何,排版是整体视觉设计的次要部分,因此 LaTeX 对整个设计的影响很小。

页面布局程序可以做到以下几点:页面(或多个页面)可以分成任意数量的方框,这些方框不必是矩形。您可以随意设计它们的形状。然后,它们会按照某种顺序链接在一起,而这些顺序不必与明显的页面位置相对应。

文本放置在第一个(按顺序)框中。它将流动和换行,以适应该框的形状。我不知道是否可以使用凹框,但肯定可以使用任何凸形。任何不适合的内容都会自动按顺序流动到第二个框,并填充该框。任何不适合第二个框的内容都会流动到第三个框。这些框不需要物理连接,甚至不需要相邻。

这可以针对一页上的任意数量的框来完成(就像一张海报),或者可以针对任意数量的页面来完成(就像一本书)。

因此,在《霍比特人》的例子中,设计师会从包含标题和龙的背景图像开始。此背景图像是绘图指南;它不会打印。然后,它被一个矩形网格覆盖,该网格定义了书的“页面”。如果“页面”不在图像上,则它是一个矩形框。但是,如果“页面”的一部分位于背景图像上,则将其分成勾勒出图像轮廓的框。然后按顺序标记所有框。

无需编程。任何有图形艺术专业知识的人都可以做到。软件会移动文本。这是印刷杂志的常规任务。人们靠做这件事谋生,我听说这不是一份特别高薪的工作。

因此,使用 LaTeX 进行这项工作就像重新发明轮子,但功能较少。这让我想起:5 万年前,我的祖先 Ooog 重新发明了轮子。你看,第一个轮子是矩形的,所以每转一圈就会有 4 个凸起。Ooog 制作了一个三角形轮子,每转一圈只有 3 个凸起。但尽管有所改进,它还是没有成功。

答案3

嗯,一种可能是使用透明的图像。您需要编辑图像,kaefer.jpg将动物的颜色更改为白色,并使背景透明。如何做到这一点取决于您使用的软件......书名也一样:在您的图像软件中写下书名,将颜色改为白色,背景透明......

现在您可以使用包textpos将图像放置在准备好的书籍文本上。

使用以下 MWE(文件 test.png包含我刚从互联网上复制的透明图像,文件test2.pdf包含书的所有页面):

\documentclass{article}

\usepackage[a0paper,margin=0cm]{geometry}

\usepackage{pdfpages}
\usepackage{graphicx}
\usepackage[%
  absolute,overlay, % <=======================================
% showboxes,
]{textpos} % <================================================

\begin{document}
\begin{textblock*}{500pt}(1cm,15cm) % <=======================
\includegraphics[width=50cm]{test.png} % <====================
\end{textblock*}

\includepdf[pages=-,nup=14x14,delta=-2cm -3cm]{test2.pdf}

\end{document}

您将获得以下结果(复制的图像是黑色的,我没有软件可以根据您的需要准备图像,但例如gimp它应该可以):

叠加图像

\begin{textblock*}{500pt}(1cm,15cm)请根据您的需要调整值。您可以在终端/控制台上textpos输入更多信息。texdoc textpos

相关内容