PDF 문서는 상당히 강력한 기능을 제공하지만, 많은 내용이 담긴 문서의 경우에는 그 용량 또한 무지하지 못할 정도로 커지게 되므로, 웹 상에서 배포할 할 때는 파일이 클라이언트로 모두 다운로드될 때 까지 대기 해야만 합니다.

구글도서나 YES24와 같은 사이트에는 즉시 응답성을 위해서 이미지 포맷을 이용하여 미리보기를 제공하여 즉시 응답성을 높이고 있는데요. 그래서 PDF 문서를 이미지로 변환하여 웹 상에서 제공할 수 있지 않을까 하고 생각해 보았습니다.

이미지로 변환하는 기능은 Acrobat에서 제공하고 있으므로, C#에서도 Acrobat의 기능을 이용하여 PDF를 이미지로 변경하는 것이 가능합니다.
이미지로 변환하기 위해서는 우선 Adobe Acrobat이 설치되어 있어야 합니다.
애석하게도 Acrobat Reader는 Acrobat관련 인터페이스를 인스턴스화 할 수 있는 COM 인터페이스를 노출하지 않으므로, 사용할 수 가 없습니다.

이미지 변환을 위해서 우선 프로젝트에 Acrobat Type Library를 COM 항목에서 참조로 추가합니다.
아래와 같이 Acrobat 또는 Adobe Acrobat Type Library를 참조로 추가하여 주세요.


그리고, 변환을 하는 코드는 아래와 같습니다.

// 아크로벳 문서­ 객체를 생성하고, 지정한 파일명의 PDF 문서­를 읽어들입니다.

Acrobat.AcroPDDoc pdfDoc = new Acrobat.AcroPDDoc();

bool ret = pdfDoc.Open(fileName);

if (ret == false)

{

    throw new FileNotFoundException();

}

 

// 아크로벳 문서­의 전체 페이지 수를 가져옵니다.

int totalPage = pdfDoc.GetNumPages();

int currentPage = 0;

 

// 문서­에서­ 지정한 번호의 페이지를 아크로벳 페이지 객체로 받아옵니다.

Acrobat.AcroPDPage pdfPage = (Acrobat.AcroPDPage)pdfDoc.AcquirePage(currentPage);

 

// 이미지로 만들기 위한 영역을 지정합니다.

Acrobat.AcroPoint pdfSize = (Acrobat.AcroPoint)pdfPage.GetSize();

Acrobat.AcroRect pdfRect = new Acrobat.AcroRect();

pdfRect.Left = 0;

pdfRect.right = pdfSize.x;

pdfRect.Top = 0;

pdfRect.bottom = pdfSize.y;

 

// 페이지의 지정된 영역을 100% 크기로 클립보드에 이미지 형태로 복사합니다.

pdfPage.CopyToClipboard(pdfRect, 0, 0, 100);

 

// 클립보드에 저장된 이미지를 파일로 저장합니다.

IDataObject clipboardData = Clipboard.GetDataObject();

if (clipboardData.GetDataPresent(DataFormats.Bitmap))

{

    Bitmap pdfBitmap = (Bitmap)clipboardData.GetData(DataFormats.Bitmap);

    pdfBitmap.Save("1.jpg");

}

문제가 발생 시에는 댓글을 남겨주세요.

Posted by WHiSTLE
TAG C#, PDF

트랙백 주소 http://blog.ntils.com/trackback/51 관련글 쓰기

댓글을 달아 주세요

  1. 2010/03/03 10:40

    Acrobat SDK 9를 인스톨 하는데... 비쥬얼스튜디오 2005가 깔려 있어야 된다는 메세지가 나오는군요...

    쥔장님 께서는 위의 소스를 2008에서 개발한건지? 아님 2005에서 개발한건지?

    2008에서 했다면 어떻게 하셨는지? 궁금합니다.

    • 2010/03/04 08:47

      Acrobat SDK 를 이용하려고 하시는 군요..
      저같은 경우에는 컴퓨터에 Acrobat이 설치되어 있는지라, SDK의 설치는 필요가 없었습니다.
      그리고, 개발은 Visual Studio 2010에서 진행하였구요.
      SDK 설치에 대해서는 테스트후에 다시 답변을 드리도록 하겠습니다.^^;

  2. 2010/03/04 09:29

    빠른 답변 감사 드립니다.

    Acrobat Reader만 설치된 컴에서는
    Acrobat.AcroPDDoc pdfDoc = new Acrobat.AcroPDDoc();
    이부분에서 COM Exception이 발생합니다 ㅠ.ㅠ

    당췌 어떻게 해야 할지 쩝~~!

    PDF --> Image.. 검색을 해보니 상용 패키지 뿐이여서...쩝~!

    Reader만 깔린컴에서 Load가 되면.. 이를 이용해서... 이미지를 가져올수 있다 생각했는데.. 쉽지가 않군요...

    쥔장님 다른 방법은 없겠습니까?

    • 2010/03/08 10:35

      답변이 조금 늦었네요.
      찾아보니, Acrobat Reader는 COM 인터페이스를 노출하지 않으므로 위 코드를 사용할 수가 없겠네요.
      위 코드를 실행하기 위해서는 Adobe Acrobat이 설치되어 있어야만 합니다. 내용은 수정해 놓도록 하겠습니다.
      원하시는 답변을 못 드려 죄송합니다.

  3. 2010/08/26 17:59

    안녕하세요.. pdf에 관해 공부 중인 학생입니다.
    다름이 아니라 님 소스 중에서 제가 돌려 보니 계속 이부분에 서 터집니다.ㅠ 왜 그런 지 모르겠습니다.ㅠ
    제가 너무 초보라서요.ㅠ
    Acrobat.AcroPDDoc pdfDoc = new Acrobat.AcroPDDoc();
    그리고 정말 죄송합니다만 pdf에 관련된 자료 같은게 있으면 저한테 메일로 좀 보내주시면 안될까요?!ㅠ
    부탁좀드리겠습니다.ㅠ
    tjdgnszz7@naver.com

    • 2010/09/07 15:52

      혹시 개발PC에 Acrobat이 설치가 안되어 있지 않나요?
      위 방법은 실행하는 PC에 Acrobat이 꼭 설치되어 있어야 합니다.(Adobe Reader 나 Acrobat Reader가 설치되어 있어서는 되지 않습니다.)

  4. 2010/08/29 06:18

    음 제 경우에는 클립보드에서 이미지를 가져오는 데서 많은 어려움이 있었습니다. Bitmap이 System.Drawing.Bitmap인가요? 타입 변환이 안 되서 var로 받아온 뒤 살펴보니 BitmapSource더라구요...
    근데 3.5이후에는 BitmapSource로 가져오는게 버그가 있다고.. 하네요; 엄청 고생했습니다.
    http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/10589cd6-6a48-4497-8f7d-a12490f376c6
    여기서 링크된 곳 여기 저기 들어가서 결국 해결했죠;

    • 2010/09/07 15:55

      그렇군요.. BitmapSource면 WPF에서 하셨나요?
      Windows Forms Project에서 테스트한거라, 그럴지도 모르겠네요.
      댓글 감사드립니다.

때때로 작성한 메서드를 과연 어디서 호출하는지 알고 싶을 때가 있습니다.
동일한 어셈블리 내에서의 호출일 수 도 있겠지만, public으로 노출된 메서드라면 현재 어셈블리 외의 외부에서도 호출이 가능합니다.

이럴 때 현재 메서드를 어느 어셈블리에서 호출하였는지를 알 수 있는 방법이 있습니다.
이미 알고 계시는지는 모르겠지만, Assembly 클래스의 정적메서드인 GetCallingAssembly 메서드가 이 방법을 제공하고 있습니다.

더보기

저작자 표시 동일 조건 변경 허락
Posted by WHiSTLE

트랙백 주소 http://blog.ntils.com/trackback/48 관련글 쓰기

댓글을 달아 주세요

응용 프로그램을 작성하다 보면 운영체제의 기본폰트로 등록되지 않은 폰트를 사용해야 할 경우가 있습니다. 이 경우 보통은 설치프로그램에서 폰트를 운영체제에 등록하여 사용하죠.
하지만, 시스템상에 폰트를 많이 설치하면 그만큼 시스템이 평소에 로드하고 있어야 하는 폰트가 많아 지므로 시스템이 느려지게 될 것이고, 자주 사용하지 않는 폰트파일이라면 굿이 등록시켜 사용할 필요가 없습니다.

그렇다면, 폰트파일을 운영체제에 등록하지 않고, 필요 할때만 잠시 로드하여 사용하면 좋겠죠?
이 부분을 지원을 지원하기 위해서 .NET Framework 에서는  System.Drawing.Text.PrivateFontCollection 클래스를 제공하고 있습니다.

PrivateFontCollection 클래스는 운영체제에 등록된 폰트가 아닌 같은 이름의 개인 버전 폰트를 사용할 수 있도록 해주고, 운영체제에 등록되지 않은 글꼴을 임시로 설치해서 사용할 수 있도록 해줍니다.

다음은 운영체제에 등록되지 않은 3of9_new.ttf 라는 바코드 폰트를 이용해서 Label에 바코드를 출력하는 예제입니다.

 

솔루션내에 3of9_new.ttf 가 포함되어 있으며 출력 디렉토리로 복사 속성이 항상 복사로 설정되어 있습니다.
코드를 보면

   1: using System.Data;
   2: using System.Drawing;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Windows.Forms;
   6:  
   7: namespace UsePrivateFont
   8: {
   9:     public partial class Form1 : Form
  10:     {
  11:         public Form1()
  12:         {
  13:             InitializeComponent();
  14:         }
  15:  
  16:         private void Form1_Load(object sender, EventArgs e)
  17:         {
  18:             System.Drawing.Text.PrivateFontCollection privateFonts = 
new System.Drawing.Text.PrivateFontCollection();
  19:             privateFonts.AddFontFile("3of9_new.ttf");
  20:             Font font = new Font(privateFonts.Families[0], 24f);
  21:             label1.Text = "*12315161321*";
  22:             label1.Font = font;
  23:         }
  24:     }
  25: }

코드는 간단합니다.
PrivateFontCollection 클래스의 객체를 생성하고 사용하고자 하는 폰트파일의 경로를 추가하는 것 만으로 사용준비가 완료됩니다.
그리고, 사용할때는  PrivateFontCollection 클래스의 Families 속성을 이용하여 추가한 폰트를 가져옵니다.

실행결과 입니다. 운영체제에 설치되지 않은 바코드 폰트를 이용하여 Label로의 출력이 완료되었습니다.

마지막으로 MSDN에 보면 운영체제에 설치된 컬렉션을 보려면 System.Drawing.Text.InstalledFontCollection클래스를 사용한다고 되어 있습니다.
이 두가지 클래스를 이용하면 설치된 폰트를 확인하고, 내가 원하는 폰트파일을 임시로 등록하여, 각 폰트별로 내가 원하는 텍스트에 대한 출력을 미리보기가 가능할 뿐만 아니라 포토샵과 다른 프로그램에서 프로그램이 실행 중일 동안만 사용이 가능하도록 할 수 도 있는 아래와 같은 프로그램을 만들 수 있을 듯합니다. ^^;



폰트다운로드:

Posted by WHiSTLE
TAG Barcode, Font

트랙백 주소 http://blog.ntils.com/trackback/42 관련글 쓰기

댓글을 달아 주세요

지난번 알아보았던 자동구현 프로퍼티(Auto-Implemented Properties)는 그동안 개발자들을 귀찮게 하던 멤버변수와 멤버변수에 대한 엑세스를 지원하는 프로퍼티를 생성하는데 좀 더 간결하게 표현할 수 있도록 해는 것이었다면 이번에 볼 객체와 컬렉션에 대한 이니셜라이저(Object and Collection Initializer)는 객체 또는 컬렉션의 생성을 좀더 직관적으로 할 수 있도록 해줍니다.

C# 2.0까지 객체를 생성할때 객체내의 멤버들을 동적으로 초기화 시키기 위해서는 생성자의 매개변수를 사용하거나, 생성후 객체의 각 멤버에 대해서 직접 값을 할당해주는 방법을 사용하였습니다만, C# 3.0의 Object and Collection Initializer는 생성자의 선언없이도 생성과 동시에 해당 객체나 컬렉션에 대한 초기화가 가능해지는 것입니다. 실제 코드를 보면 쉽게 이해가 가능하실 겁니다.

[코드 1-1 : C# 3.0 이전의 객체 초기화]

    1 public class Customer

    2 {

    3     private int custKey;

    4     private string name;

    5     private string address;

    6 

    7     public int CustKey { get { return custKey; } }

    8     public string Name

    9     {

   10         get { return name; }

   11         set { name = value; }

   12     }

   13     public string Address

   14     {

   15         get { return address; }

   16         set { address = value; }

   17     }

   18 }

   19 

   20 public class Program

   21 {

   22     static void Main(string[] args)

   23     {

   24         Customer customer = new Customer();

   25         customer.Name = "오성민";

   26         customer.Address = "서울시 구로구";

   27     }

   28 }

위의 코드는 언제나 보던 일반적인 코드입니다. 객체를 생성한 후 해당 객체의 멤버에 값을 할당하는 것이죠. 이 코드를 Object and Collection Initializer를 이용한 코드로 바꾼다면 아래와 같이 됩니다.

[코드 1-2 : C# 3.0 의 Object and Collection Initializer를 이용한 초기화

    1 public class Customer

    2 {

    3     public int CustKey { get; private set; }

    4     public string Name { get; set; }

    5     public string Address { get; set; }

    6 }

    7 

    8 public class Program

    9 {

   10     static void Main(string[] args)

   11     {

   12         Customer customer = new Customer

   13         {

   14             Name = "노휘겸",

   15             Address = "서울시 관악구"

   16         };

   17     }

   18 }

지난번에 보았던 자동구현 프로퍼티를 이용하여 Customer 클래스를 작성하였고, 이 클래스의 객체를 생성하는 시점에서 Customer객체의 내부 멤버들의 값을 할당하였습니다.

Object and Collection Initializer의 구성은 여러개의 멤버에 대한 이니셜라이저가 ,(콤마)로 구분되어 있으며, 이니셜라이저 외부에는 "{", "}"로 둘러싸여 구성되어 있습니다.
이니셜라이저는 접근이 가능한 멤버나 프로퍼티에 대해서만 값의 할당이 가능합니다.
즉, 위의 코드에서 본다면 set이 private 으로 구성된 CustKey의 경우에는 CustKey = 1 로 할당 할 수 없는 것입니다.
그리고, 한번에 같은 멤버를 두번이상 할당할 시에는 오류가 발생하게 됩니다.

아래는 위보다 조금 복잡한 사용예입니다.

[코드2]

    1 public class Point

    2 {

    3     public int X { get; set; }

    4     public int Y { get; set; }

    5 }

    6 

    7 public class Line

    8 {

    9     public Point P1 { get; set; }

   10     public Point P2 { get; set; }

   11 }

   12 

   13 public class Triangle

   14 {

   15     private Point p1 = new Point();

   16     private Point p2 = new Point();

   17     private Point p3 = new Point();

   18 

   19     public Point P1 { get { return p1; } set { p1 = value; } }

   20     public Point P2 { get { return p2; } set { p2 = value; } }

   21     public Point P3 { get { return p3; } set { p3 = value; } }

   22 }

   23 

   24 public class Program

   25 {

   26     static void Main(string[] args)

   27     {

   28         Line line = new Line

   29         {

   30             P1 = new Point { X = 1, Y = 1 },

   31             P2 = new Point { X = 4, Y = 4 }

   32         };

   33 

   34         Triangle triangle = new Triangle

   35         {

   36             P1 = { X = 0, Y = 0 },

   37             P2 = { X = 5, Y = 5 },

   38             P3 = { X = 5, Y = 0 }

   39         };

   40     }

   41 }

위와 같이 이니셜라이저 안에서 또다른 객체의 이니셜라이저가 존재할 수도 있으며, 이미 생성된 객체의 경우에는 값의 할당이 바로 이루어질 수도 있습니다.
line 객체의 경우 P1, P2 Point 객체가 생성되지 않았으므로 new Point {...}으로 생성과 함께 할당을 하였으나, triangle 객체의 경우에는 이미 Point 클래스의 객체들이 이미 생성되어 있으므로 생성하는 부분을 생략하고 바로 값을 할당하고 있음을 볼 수 있습니다.

만일 [코드1-2]에서와 같이 CustKey처럼 외부에서 할당이 불가능하고 생성자의 파라미터를 통해서 값을 할당해야 할때도 사용이 가능할까요?
물론 사용이 가능합니다. 생성자가 있다고 해서 달라질것은 생성자에 파라미터에 해당하는 값을 전송하면 되는 것이죠.

public class Customer

{

    public int CustKey { get; private set; }

    public string Name { get; set; }

    public string Address { get; set; }


    public Customer(int custKey)

    {

        CustKey = custKey;

    }

}


Customer customer = new Customer(1) { Name = "노휘겸", Address = "서울" };


만일 생성자에서 값이 할당된 멤버를 다시 이니셜라이저에서 값을 할당하면 이니셜라이저에서 할당한 값으로 설정이 됩니다.

이름이 Object and Collection Initializer 이므로 이제 컬렉션을 이용하는 코드를 보도록 하죠. 다들 예상하시겠지만, Collection이라고 해서 다른게 없이 위의 이니셜라이저 안에서 또 다른 이니셜라이저를 사용하는 것과 동일합니다.

    1 List<Customer> customers = new List<Customer>

    2 {

    3     new Customer(1) { Name = "노휘겸", Address = "서울" },

    4     new Customer(2) { Name = "류순필", Address = "마산" },

    5     new Customer(3) { Name = "황원준", Address = "오산" }

    6 };

Customer객체가 담기는 Generic List의 생성을 한번에 완료시켰습니다. 만일 이 코드를 C# 2.0의 코드로 작성한다면

    1 List<Customer> customers = new List<Customer>();

    2 Customer customer1 = new Customer(1);

    3 customer1.Name = "노휘겸";

    4 customer1.Address = "서울";

    5 customers.Add(customer1);

    6 ......

위와 같은 형태가 되겠죠? 보시는 여러분들이 판단하실 부분이긴 하지만, 제 주관적으로는 상당히 직관적이고 코드 읽기가 수월할 듯 하네요.

지난번 Auto-Implemented Properties 와 이번의 Object and Collection Initializer 의 경우 감탄을 자아낼 만큼 대단한 기능은 아니지만, C#의 언어의 표현력을 향상시켜주는 것 같습니다.

궁금한점이나 지적사항이 있으면 댓글로 남겨주세요.

Posted by WHiSTLE
TAG C# 3.0

트랙백 주소 http://blog.ntils.com/trackback/25 관련글 쓰기

댓글을 달아 주세요

Microsoft Patner 교육엘 다녀왔습니다. 처음 .NET을 접했을때 교육해주셨던 선생님이 Microsoft로 직장을 옮기셨는데, 이번 교육을 직접 진행하신다고 하길때 반가운 마음에 달려갔습니다.

교육은 Visual Studio 2008에 포함될 C# 3.0의 언어적인 발전부분에 대해서 Hands On Lab방식의 실습으로 이루어졌습니다. 사실 그동안 C# 3.0의 언어적인 발전부분에 대해서는 어느정도 관심은 있었지만, 게으른 관계로 깊이 공부해보지는 못했던 터라, 뜬 구름잡듯이만 알고 있었는데. 이번 교육을 통해서 개념을 잡을 수 있었던것 같습니다.

C# 3.0의 변화의 가장 핵심은 바로 Lambda Expression(람다식)의 지원이라고 할 수 있었습니다. 람다식을 지원하기 위해서 컴파일 타임의 형에 대한 추론능력이 비약적으로 발전하게 되었고, 그러한 타입에 대한 추론능력을 바탕으로 암시적 로컬 변수(Implicitly typed local variable) 및 익명타입(Anonymous Type)의 사용이 가능하게 되었으며, 개체와 컬렉션 이니셜라이저(Object and Collection Initializer)등으로 좀더 표현력이 뛰어난 언어로 발전할 수 있었던 것 같습니다.

그리고, 람다식이 가능하게 되므로서 C#언어 자체에서 데이터에 대한 SQL과 같은 구조적 질의또한 가능하게 되어 데이터의 활용이 한층 쉽고 강력해 진것도 느낄수 있더군요.

자동 구현 프로퍼티(Auto-Implemented Properties)

객체지향의 원칙중 캡슐화(Encapsulation)을 위하여 C#에서는 Property를 지원합니다. 하지만, 개발시에 private 접근자의 멤버변수와 이를 캡슐화하는 Property을 만들때 멤버변수로 접근하는 코드를 제외한 프로그래밍적인 처리가 들어가는 경우는 크게 많지 않습니다

private string name;

public string Name

{

    get { return name; }

    set { name = value; }

}

변수를 선언할때 위와 같이 거의 습관적으로 코딩하시는 분이 많을 겁니다. 저또한 거의 습관적으로 나오는 코드이고, 양이 많을 경우 매번 프로퍼티를 구현하는게 귀찮을 때도 많습니다. 물론 코드조각을 이용하시는 분들도 계시긴 하겠지만 저는 변수선언과 프로퍼티선언의 영역을 달리 하는 편이라 그것도 썩 맘에 들지 않아 그냥 손으로 코딩을 해버립니다.
물론 어쩔땐 get; set; 모두 노출해야 하는 멤버변수는 그냥 public으로 선언할 때도 있습니다.(-_-)

C# 3.0에서는 이러한 불필요한 듯한 노력을 줄여주는 자동 구현 프로퍼티가 반영되어 예전처럼 멤버변수와 프로퍼티의 구현을 기계적으로 할 필요가 없어졌습니다.
구문은 아래와 같습니다.

public string Name { get; set; }

public string Address { get; internal set; }

public string UserKey { get; private set; }

인터페이스에서 프로퍼티를 선언할 때와 거의 비슷한 구문을 가지고 있습니다만, 이는 인터페이스내에서 작성하는 코드가 아니라 클래스내에서 멤버변수의 선언과 프로퍼티의 선언을 한번에 해결할 수 있는 코드입니다.
하지만, 먼가 좀 이상하지 않으신가요? 프로퍼티란 메서드의 한 종류일뿐 분명히 변수는 아님에도 저게 어떻게 동작이 가능할까요? VB(.NET버전 이하)처럼 모든것을 포용해주는(가끔 프로그래머를 패닉상태로 몰고가기도 하죠.^^) C#이 아니지 않습니까? 깐깐한 C#이 변수도 선언하지 않았는데 메모리를 할당해 줄리는 만무 할텐데...
그것은 자동 구현 프로퍼티가 있으면 컴파일 타임에서 컴파일러가 멤버변수를 자동으로 생성해주게 되는 것입니다. 즉, 변수가 존재하지 않는게 아니라, 프로그래머가 해야할 일을 컴파일러가 대신해주는 것 뿐인거죠.

코드를 보시면 get; set;에 대하여 엑세스제한자를 지정할 수 있습니다. set에다 private를 지정할 수 있으므로 외부에서 값을 변경하는 것을 막을 수 있습니다.
하지만, 만일 set을 지정하지 않으면 내부에서 해당 변수에 접근이 불가능하기 때문에 get과 set 모두 존재해야지만 정상적인 사용이 가능합니다.

Posted by WHiSTLE
TAG C# 3.0

트랙백 주소 http://blog.ntils.com/trackback/24 관련글 쓰기

  1. 삭제

    검쉰의 생각

    2010/03/04 00:33 | Tracked from akyo's me2DAY

    C# 에 자동 구현 프로퍼티(Auto-Implemented Properties)라는게 생겼구나. 우왕 신기함. 기본서를 하나 사야되나.. 뭔가 많이 새롭네. ㄷㄷ

댓글을 달아 주세요

이전버튼 1 이전버튼