关于 C++ 打印 PDF 打印及 PDF 转图片、合并

2023-11-17

原文: http://www.aqcoder.com/post/content?id=42

pdf(Portable Document Format 的简称,意为“便携式文档格式”),是由 Adobe Systems 用于与应用程序、操作系统、硬件无关的方式进行文件交换所发展出的文件格式。PDF 文件以 PostScript 语言图象模型为基础,无论在哪种打印机上都可保证精确的颜色和准确的打印效果,即 PDF 会忠实地再现原稿的每一个字符、颜色以及图象。

PDF 设计的初衷是为了解决多平台的打印问题,所以他不像 WORD 那样具有文档流结构,编辑方面没有 WORD 那样强大。

打印

需要打印 PDF 自然需要一个打印程序,可以打印 PDF 的程序有很多。Adobe 自家的有 PDF Reader、 Acrobat。商用的有金山 WPS、Foxit Reader。开源的有 xpdf reader 等。

当然,作为程序猿我们说的打印自然不是讨论如何使用这些软件打印 PDF。而是这么使用程序打印 PDF。

应用场景:第三方给你的系统提供了 PDF 格式的文件,在你的系统中有个打印按钮,点击之后去打印这个 PDF。

实现方法一

在客户端上安装一个 PDF 渲染程序(上面的几个 PDF 浏览器/编辑器程序都含有渲染功能),然后使用命令行调用打印。各种语言启动进程的方法这里就不赘述了。

显然,这种方法有个很大的缺点,要求客户机上必须安装一个渲染程序,或者吧他的程序包在你的程序包里,这样会有版权问题,还会使你的程序包变大。

实现方法二

讲方法二前,我们先来说说网传的一种方法

if (OpenPrinterA("Microsoft XPS Document Writer", &hPrinter, NULL))
{
    if (StartDocPrinterA(hPrinter, 1, (LPBYTE)&di))
    {
        if (StartPagePrinter(hPrinter))
        {
            bSuccess = WritePrinter(hPrinter, buffer, length, &dwWritten);
            EndPagePrinter(hPrinter);
        }
        EndDocPrinter(hPrinter);
    }
    ClosePrinter(hPrinter);
}
if (bSuccess == false)
{
    dwError = GetLastError();
}

这是 C++ 的实现网上还有一个 C# 的实现。

这种方法是行不通的 ,你会发现有的人在帖子里回复何以,有的人回复不可以。这是为什么呢。

因为打印机一般只识别打印格式,如 PostScript 格式,而 PDF 格式是在 PostScript 格式之上的。但是有些新型的打印机也会识别 PDF 格式。这就是为什么有的人测试不可以,有的人测试可以的原因。

现在我们来讨论方法二。直接发送 PDF 文件到打印机是行不通的,所以只能乖乖的使用打印机 GDI 接口,吧 PDF 渲染到打印机 DC 上。

所以使用这个方法的前提是需要一个 PDF 渲染器,pdfium 是 Google Chromium 的项目的一部分,也是一个 PDF 渲染器,我们可以使用 pdfium 解析 PDF 文件,并渲染到打印机 DC 上。

由于这种方法工程量有点大,所以我最终选择了第三种方法。

方法三

既然使用一个 PDF 渲染器工程量比较大,那么我们是否可以吧 PDF 文件变成图片呢。把图片渲染到打印机 DC 上就很容易了。

 -----       ------        -------------
| pdf | --> | image | --> | print DC API |
 -----       ------        -------------

由于我们后端是用 JAVA 实现的,所以转化这一步自然也可以挪到后端去做,后端我们选择了 Apache PDFBox 作为转化库。PDFbox 转化为图片就很容易了:

public static List<BufferedImage> toImageList(InputStream pdf)
      throws InvalidPasswordException, IOException {
    int dpi = 96;
    PDDocument document = PDDocument.load(pdf);
    PDFRenderer renderer = new PDFRenderer(document);
    renderer.setSubsamplingAllowed(true);
    List<BufferedImage> ret = new ArrayList<BufferedImage>();
    for (int i = 0; i < document.getNumberOfPages(); i++) {
      BufferedImage bi = renderer.renderImageWithDPI(i, dpi, ImageType.RGB);
      ret.add(bi);
    }
    document.close();
    return ret;
  }

通过以上代码我们发现 PDFBox 也是一个 PDF 渲染器,但是客户端程序一般不会去用 JAVA 做,若果你的客户端程序是 JAVA 写的,哪可直接使用打印功能了。

那么在 C++ 里打印图片功能如何实现呢:

// data 为图片数据,这里我们使用图片 base64 数据,注意要去掉 base64 头部。
bool PrintImage(const std::string& printer, const std::string& data) {
  bool bAtoFit = true;
  char szDriver [16] = "WINSPOOL";
  char szPrinter [1024];
  DWORD cchBuffer = 255;
  HDC hdcPrint = NULL;
  HANDLE hPrinter = NULL;
  PRINTER_INFO_2A * pPrinterData;
  BYTE pdBuffer [102400];
  BOOL bReturn = FALSE;

  DWORD cbBuf = sizeof(pdBuffer);
  DWORD cbNeeded = 0;
  pPrinterData =(PRINTER_INFO_2A *)&pdBuffer[0];

  if (printer.empty())
      GetDefaultPrinterA(szPrinter, &cchBuffer);
  else
      strcpy_s(szPrinter, printer.c_str());

  if(!OpenPrinterA(szPrinter, &hPrinter, NULL))
  {
      return false;
  }

  if(GetPrinterA(hPrinter, 2, &pdBuffer[0], cbBuf,&cbNeeded))
      ClosePrinter(hPrinter);
  else
  {
      callback->Failure(-1, "get printer fail.");
      return false;
  }

  hdcPrint = CreateDCA(szDriver, szPrinter, pPrinterData->pPortName, NULL);
  CDC dc;
  if (!dc.Attach(hdcPrint)) {
      callback->Failure(-1, "No printer found!");
      return false;
  }

  dc.m_bPrinting = TRUE;
  DOCINFO di;
  // Initialise print document details
  ::ZeroMemory (&di, sizeof (DOCINFO));
  di.cbSize = sizeof (DOCINFO);
  di.lpszDocName = L"cef_print_image";
  // Begin a new print job
  BOOL bPrintingOK = dc.StartDoc(&di);
  // Get the printing extents
  // and store in the m_rectDraw field of a 
  // CPrintInfo object
  CPrintInfo Info;
  // just one page
  Info.SetMaxPage(1);
  int maxw = dc.GetDeviceCaps(HORZRES);
  int maxh = dc.GetDeviceCaps(VERTRES);
  Info.m_rectDraw.SetRect(0, 0, maxw, maxh);
  for (UINT page = Info.GetMinPage(); page <= Info.GetMaxPage() && bPrintingOK; page++)
  {
      // begin new page
      dc.StartPage();
      Info.m_nCurPage = page;

      // get bitmap
      CefRefPtr<CefBinaryValue> pData = CefBase64Decode(data);
      size_t lenDes = pData->GetSize();
      char* pDes = new char[lenDes];
      pData->GetData(pDes, lenDes, 0);

      BITMAP bm;
      CBitmap* pbmp = NULL;
      HBITMAP Hbitmap = NULL;
      HGLOBAL hMem = ::GlobalAlloc(GMEM_MOVEABLE, lenDes);
      LPVOID pImage = ::GlobalLock(hMem);
      memcpy(pImage, pDes, lenDes);
      IStream* pStream = NULL;
      ::CreateStreamOnHGlobal(hMem, FALSE, &pStream);
      Gdiplus::Bitmap gdi(pStream);
      gdi.GetHBITMAP(NULL, &Hbitmap);
      pbmp = CBitmap::FromHandle(Hbitmap);
      pbmp->GetBitmap(&bm);

      int w = bm.bmWidth;
      int h = bm.bmHeight;
      float rate = (float)maxw / w;
      // create memory device context
      CDC bmpDC;
      bmpDC.CreateCompatibleDC(&dc);
      bmpDC.SetMapMode(dc.GetMapMode());
      CBitmap *pBmp = bmpDC.SelectObject(pbmp);

      dc.SetStretchBltMode(STRETCH_DELETESCANS);
      // now stretchblt to maximum width on page
      if (bAutoFit)
          dc.StretchBlt(0, 0, maxw, maxh, &bmpDC, 0, 0, w, h, SRCCOPY);
      else
          dc.StretchBlt(0, 0, maxw, int(h * rate), &bmpDC, 0, 0, w, h, SRCCOPY);
      // clean up
      bmpDC.SelectObject(pBmp);
      pStream->Release();
      GlobalUnlock(hMem);
      GlobalFree(hMem);
      delete[] pDes;
      bPrintingOK = (dc.EndPage() > 0);   // end page
  }

  if (bPrintingOK)
  {
      dc.EndDoc(); // end a print job
      return true;
  }
  else
  {
      dc.AbortDoc();
      return false;
  }
}

用 C# 实现打印图片的功能估计会更容易一些。

合并问题

有时候我们会遇到需要将多个 PDF 合并到一起,PDFbox 有合并的功能:

public static void mergePdf(List<InputStream> pdfList, OutputStream out) throws IOException {
  PDFMergerUtility merger = new PDFMergerUtility();
  merger.addSources(pdfList);
  merger.setDestinationStream(out);
  merger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
}

假设有两份 PDF 文件 pdf1,pdf2 这两份 PDF 文件都只含有一页,并且内容只有一行文字:

pdf1

图 2-1 pdf1

pdf2

图 2-2 pdf1

合并以后将会得到一份两页的 PDF 文件。这是因为 PDF 并不是 流式文档结构 的。

文档结构

摘自百度百科

PDF文件结构主要可以分为四个部分:

首部

用文本编辑器打开的时候就可以看到:%PDF-1.4 这样的字眼,其中最后一位就是PDF文件格式版本号,软件的版本号总要比文件格式的版本号高1,比如说Read 5能打开的内容就是4。
文件体

里面由若干个的obj对象来组成,类似这种形式:

3 0 obj
<<
/Type /Pages
/Count 1
/Kids [4 0 R]
>>
endobj

第一个数字称为对象号,来唯一标识一个对象的,第二个是产生号,是用来表明它在被创建后的第几次修改,所有新创建的PDF文件的产生号应该都是0,即第一次被创建以后没有被修改过。上面的例子就说明该对象的对象号是3,而且创建后没有被修改过。

对象的内容应该是包含在<< 和>>之间的,最后以关键字endobj结束。

交叉引用表

用来索引各个obj 对象在文档中的位置,以实现随机访问,它的形式是:

xref
0 8
0000000000 65535f
0000000009 00000n
0000000074 00000 n
0000000120 00000 n
0000000179 00000 n
0000000322 00000 n
0000000415 00000 n
0000000445 00000 n

xref说明一个交叉引用表的开始,交叉引用表的第一行0 8 说明下面各行所描述的对象号是从0开始,并且有8个对象。

0000000000 65535f,一般每个PDF文件都是以这一行开始交叉应用表的,说明对象0的起始地址为0000000000,产生号(generation number)为65535,也是最大产生号,不可以再进行更改,而且最后对象的表示是f, 表明该对象为free, 这里,大家可以看到,其实这个对象可以看作是文件头。

0000000009 00000n就是表示对象1,0000000009是其偏移地址,00000为5位产生号(最大为65535),0表明该对象未被修改过, n表示该对象在使用,区别与自由对象(f),可以更改。

尾部

Trailer
<<
/Size 8
/Root 1 0 R
>>
startxref
553
%%EOF

trailer 说明文件尾 trailer对象的开始。

/Size 8说明该PDF文件的对象数目。

/Root 1 0 R说明根对象的对象号为1。

Startxref

553说明交叉引用表的偏移地址,从而可以找到PDF文档中所有的对象的相对地址,进而访问对象。

%%EOF为文件结束标志。

图片合并

那么我们怎么才能得到我们想要的合并的效果呢,这里有个很土的方法。使用绘制图片的方法,倒着遍历图片,发现像素不是 (255,255,255) 则记录位置,讲下一张图片从这个偏移点开始绘制。最终得到的效果如下:

merge

图 2-3 合并图片

public static List<BufferedImage> mergeImage(List<BufferedImage> imgList, MergeImageOptions options)
      throws IOException {
  if (options == null) {
    options = new MergeImageOptions();
  }

  List<BufferedImage> ret = new ArrayList<BufferedImage>();
  BufferedImage mergeImg = null;
  int imgWidth = options.getWidth();
  int imgHeight = options.getHeight();
  int offset = 0;
  for (int p = 0; p < imgList.size(); p++) {
    BufferedImage bi = imgList.get(p);
    int width = bi.getWidth();
    int height = bi.getHeight();
    int minx = bi.getMinX();
    int miny = bi.getMinY();
    int realHeight = height;
    boolean done = false;
    for (int j = height - 1; j > miny; j--) {
      for (int i = minx; i < width; i++) {
        int pixel = bi.getRGB(i, j);
        int r = (pixel & 0xff0000) >> 16;
        int g = (pixel & 0xff00) >> 8;
        int b = (pixel & 0xff);
        if (r != 255 || g != 255 || b != 255) {
          realHeight = j;
          done = true;
          break;
        }
      }
      if (done) {
        break;
      }
    }
    if (p == 0) {
      imgWidth = Math.max(imgWidth, width);
      imgHeight = Math.max(imgHeight, height);
    }

    if (mergeImg == null || (offset + realHeight) > imgHeight) {
      mergeImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
      ret.add(mergeImg);
      offset = 0;
    }

    Graphics g = mergeImg.getGraphics();
    g.drawImage(bi, minx, offset, null);
    offset += realHeight;
    g.dispose();
  }
  return ret;
}

介绍几个关于 PDF 的实用库

ravenq

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

关于 C++ 打印 PDF 打印及 PDF 转图片、合并 的相关文章

  • WPF DataGrid 多选

    我读过几篇关于这个主题的文章 但很多都是来自 VS 或框架的早期版本 我想做的是从 dataGrid 中选择多行并将这些行返回到绑定的可观察集合中 我尝试创建一个属性 类型 并将其添加到可观察集合中 它适用于单个记录 但代码永远不会触发多个
  • BASIC 中的 C 语言中的 PeekInt、PokeInt、Peek、Poke 等效项

    我想知道该命令的等效项是什么Peek and Poke 基本和其他变体 用 C 语言 类似PeekInt PokeInt 整数 涉及内存条的东西 我知道在 C 语言中有很多方法可以做到这一点 我正在尝试将基本程序移植到 C 语言 这只是使用
  • STL 迭代器:前缀增量更快? [复制]

    这个问题在这里已经有答案了 可能的重复 C 中的预增量比后增量快 正确吗 如果是 为什么呢 https stackoverflow com questions 2020184 preincrement faster than postinc
  • 在 xaml 中编写嵌套类型时出现设计时错误

    我创建了一个用户控件 它接受枚举类型并将该枚举的值分配给该用户控件中的 ComboBox 控件 很简单 我在数据模板中使用此用户控件 当出现嵌套类型时 问题就来了 我使用这个符号来指定 EnumType x Type myNamespace
  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • 传递给函数时多维数组的指针类型是什么? [复制]

    这个问题在这里已经有答案了 我在大学课堂上学习了 C 语言和指针 除了多维数组和指针之间的相似性之外 我认为我已经很好地掌握了这个概念 我认为由于所有数组 甚至多维 都存储在连续内存中 因此您可以安全地将其转换为int 假设给定的数组是in
  • 如何使从 C# 调用的 C(P/invoke)代码“线程安全”

    我有一些简单的 C 代码 它使用单个全局变量 显然这不是线程安全的 所以当我使用 P invoke 从 C 中的多个线程调用它时 事情就搞砸了 如何为每个线程单独导入此函数 或使其线程安全 我尝试声明变量 declspec thread 但
  • 需要帮助优化算法 - 两百万以下所有素数的总和

    我正在尝试做一个欧拉计划 http projecteuler net问题 我正在寻找 2 000 000 以下所有素数的总和 这就是我所拥有的 int main int argc char argv unsigned long int su
  • C# 列表通用扩展方法与非通用扩展方法

    这是一个简单的问题 我希望 集合类中有通用和非通用方法 例如List
  • 在 Unity 中实现 Fur with Shells 技术

    我正在尝试在 Unity 中实现皮毛贝壳技术 http developer download nvidia com SDK 10 5 direct3d Source Fur doc FurShellsAndFins pdf Fins 技术被
  • 结构体的内存大小不同?

    为什么第一种情况不是12 测试环境 最新版本的 gcc 和 clang 64 位 Linux struct desc int parts int nr sizeof desc Output 16 struct desc int parts
  • C# xml序列化必填字段

    我需要将一些字段标记为需要写入 XML 文件 但没有成功 我有一个包含约 30 个属性的配置类 这就是为什么我不能像这样封装所有属性 public string SomeProp get return someProp set if som
  • 空指针与 int 等价

    Bjarne 在 C 编程语言 中写道 空指针与整数零不同 但 0 可以用作空指针的指针初始值设定项 这是否意味着 void voidPointer 0 int zero 0 int castPointer reinterpret cast
  • 如何在 Android 中使用 C# 生成的 RSA 公钥?

    我想在无法假定 HTTPS 可用的情况下确保 Android 应用程序和 C ASP NET 服务器之间的消息隐私 我想使用 RSA 来加密 Android 设备首次联系服务器时传输的对称密钥 RSA密钥对已在服务器上生成 私钥保存在服务器
  • 有没有办法让 doxygen 自动处理未记录的 C 代码?

    通常它会忽略未记录的 C 文件 但我想测试 Callgraph 功能 例如 您知道在不更改 C 文件的情况下解决此问题的方法吗 设置变量EXTRACT ALL YES在你的 Doxyfile 中
  • 在 WPF 中使用 ReactiveUI 提供长时间运行命令反馈的正确方法

    我有一个 C WPF NET 4 5 应用程序 用户将用它来打开某些文件 然后 应用程序将经历很多动作 读取文件 通过许多插件和解析器传递它 这些文件可能相当大 gt 100MB 因此这可能需要一段时间 我想让用户了解 UI 中发生的情况
  • 为什么 std::uint32_t 与 uint32_t 不同?

    我对 C 有点陌生 我有一个编码作业 很多文件已经完成 但我注意到 VS2012 似乎有以下语句的问题 typedef std uint32 t identifier 不过 似乎将其更改为 typedef uint32 t identifi
  • 指针和内存范围

    我已经用 C 语言编程有一段时间了 但对 C 语言还是很陌生 有时我对 C 处理内存的方式感到困惑 考虑以下有效的 C 代码片段 const char string void where is this pointer variable l
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么
  • 从 mvc 控制器使用 Web api 控制器操作

    我有两个控制器 一个mvc控制器和一个api控制器 它们都在同一个项目中 HomeController Controller DataController ApiController 如果我想从 HomeController 中使用 Dat

随机推荐

  • Ubuntu18.04 谷歌浏览器安装教程

    Ubuntu 经验笔记 Ubuntu18 04 谷歌浏览器安装教程 1 测试环境 2 安装步骤 Ubuntu18 04 谷歌浏览器安装教程 1 测试环境 系统版本 Ubuntu 18 04 安装时间 2021年7月4日 2 安装步骤 启动终
  • 区块链学习笔记(八)——应用之有机大米的一生

    区块链学习笔记 八 应用之有机大米的一生 前言 一 张三申请加入村里合作社的有机大米农业区块链项目 二 大米种植全程上链 三 有机大米收获出售过程上链 总结 前言 其实区块链的现实应用很多 我们用有机大米种植销售为例来看看它的应用 麻将四人
  • 设备怎样开启位置服务器,开启设备服务器

    开启设备服务器 内容精选 换一换 使用远程登录方式连接登录Windows云服务器时出现如下错误 此计算机无法连接到远程计算机 服务端安全组3389端口未开启 检查云服务器端口配置 服务端防火墙关闭 检查防火墙配置是否正常远程桌面连接配置不正
  • BMP存储方式

    BMP存储像素值的方式为从下至上 从左至右 紧随着文件头存储的字节为图像最下一行的数值 从左下角开始依次存储 22 22 22 23 为图像左下角像素的数值 依次向右存储 最后一行扫描完后 紧接着存储上一行 最后一个byte存储的是图像右上
  • Java中位数

    中位数 输入数组长度n 和n个数 输出这n个数的中位数 当结果为小数时向下取整 输入用例 1 1 输出用例 1 输入用例 2 3 3 输出用例 3 输入用例 5 5 3 1 2 4 输出用例 3 import java util Scann
  • 【ESP-IDF】2.ESP32C3移植u8g2显示库驱动OLED

    前言 这个系列的文章属于是为了一碟醋包了一顿饺子系列 起因是看到tb上某家店的ESP32C3开发板才9 9包邮 想着研究一下 把手头有个用Arduino UNO实现的项目升级一下 于是就有了这个系列 ESP32C3的简介 2020 年末 乐
  • React Navigation(三)-StackActions(API)

    原文链接 StackActions对象包含了生成特定actions的方法 即基于栈导航器的actions 这些方法扩展了NavigationActions 支持以下actions Reset 用一个新的状态替换当前状态 Replace 用其
  • Python 人脸表情识别

    人脸表情识别 一 图片预处理 二 数据集划分 三 识别笑脸 四 Dlib提取人脸特征识别笑脸和非笑脸 参考 环境搭建可查看Python人脸识别微笑检测 数据集可在https inc ucsd edu mplab wordpress inde
  • 阿里云CDN缓存预热与刷新以及常见的故障汇总

    文章目录 1 为CDN缓存的文件增加过期时间 2 CDN缓存预热配置 3 CDN缓存刷新配置 4 常见故障 CDN缓存预热指的是主动将要缓存的文件推送到全国各地的CDN边缘加速器上 减少回源率 提供命中率 缓存刷新指的是后期上传了同名的文件
  • ubuntu9.10 虚拟机连接windows网络上网,以及NFS挂载网络设置。

    1 虚拟机设置 2 关掉网卡 sudo ifconfig ethxx down 3 打开网卡 sudo ifconfig ethxx up 4 打开浏览器就可以使用网络上网了 NFS 1 vmware软件设置网络连接方式 2 选择桥接方式
  • 写了placement new也要写placement delete——条款52

    placement new和placement delete并非C 兽栏中最常见的动物 如果你不熟悉它们 不要感到挫折或忧虑 回忆条款16和17 当你写一个new表达式像这样 Widget pw new Widget 共有两个函数被调用 一
  • 映射表原理分析与总结

    在使用本地缓存时 经常用到映射表 大家都知道映射表保存数据的原理是将key做hash再取余 余数落在数组的不同索引中 利用数组的索引获取元素 时间复杂度为O 1 这样查询速度很快了 但是也存在一个问题 那就是如果两个key落到同一个索引桶上
  • 使用js获取上传文件的真实路径

    我们在使用html中的
  • 闭关之现代 C++ 笔记汇总(二):特性演化

    目录 前言 C 98 C 98 之前 C 98 的主要语言特性 特性总结 dynamic cast RAII 标准库组件 总结 find if 其他语言对 C 影响 非 C 98 内容 C 对其他语言影响 非 C 98 内容 C 11 C
  • java jre jvm_JVM、JRE和JDK的关系

    JVM Java Virtual Machine是Java虚拟机 Java程序需要运行在虚拟机上 不同的平台有自己的虚拟机 因此Java语言可以实现跨平台 JRE Java Runtime Environment包括Java虚拟机和Java
  • 并发问题(二)什么是并发

    1 什么是并发操作 并发操作是指同一时间可能有多个用户对同一数据进行读写操作 2 并发操作对数据的影响 如果对并发操作不做任何控制的话 会造成数据的不完整性 可能造成读脏数据 不可重复读 丢失修改还有幻读 3 对数据不完整性的举例 1 丢失
  • Java Spring Boot 框架

    Java Spring Boot 框架 Spring Boot是一个用于快速构建独立 生产级别的Java应用程序的开源框架 它是Spring Framework的扩展 旨在简化Spring应用程序的开发和部署 并提供一个约定优于配置的开发模
  • MYSQL5.1 WINDOWS环境下导出查询数据到EXCEL文件

    今天做一个多表的联合查询 用myadmin不支持导出 于是找到下面的方法 不错 查询出来的记录 导出到EXCEL文件 直接做报表输出 测试环境WINDOWS XP OFFICE 2003 MYSQL 5 1 451 创建一个测试表 3个字段
  • 利用gcc-arm-none-eabi开源工具链开发STM32程序

    一 前言 入门STM32开发时 用的是keil 这个IDE 后面因为要提高开发效率和keil 版权问题 选择开源的arm none eabi gcc 通过命令行调用make工具进行编译 链接 烧录 打包 二 要达到的效果 2 1 编译STM
  • 关于 C++ 打印 PDF 打印及 PDF 转图片、合并

    原文 http www aqcoder com post content id 42 pdf Portable Document Format 的简称 意为 便携式文档格式 是由 Adobe Systems 用于与应用程序 操作系统 硬件无